排序题目:丢失的数字

题目

标题和出处

标题:丢失的数字

出处:268. 丢失的数字

难度

2 级

题目描述

要求

给定一个数组 nums \texttt{nums} nums,数组包含 n \texttt{n} n 个在范围 [0,   n] \texttt{[0, n]} [0, n] 内的不同整数,返回该范围内唯一没有在数组中出现的数。

示例

示例 1:

输入: nums   =   [3,0,1] \texttt{nums = [3,0,1]} nums = [3,0,1]
输出: 2 \texttt{2} 2
解释: n   =   3 \texttt{n = 3} n = 3,因为有 3 \texttt{3} 3 个数字,所以所有的数字都在范围 [0,3] \texttt{[0,3]} [0,3] 内。 2 \texttt{2} 2 是丢失的数字,因为它没有出现在 nums \texttt{nums} nums 中。

示例 2:

输入: nums   =   [0,1] \texttt{nums = [0,1]} nums = [0,1]
输出: 2 \texttt{2} 2
解释: n   =   2 \texttt{n = 2} n = 2,因为有 2 \texttt{2} 2 个数字,所以所有的数字都在范围 [0,2] \texttt{[0,2]} [0,2] 内。 2 \texttt{2} 2 是丢失的数字,因为它没有出现在 nums \texttt{nums} nums 中。

示例 3:

输入: nums   =   [9,6,4,2,3,5,7,0,1] \texttt{nums = [9,6,4,2,3,5,7,0,1]} nums = [9,6,4,2,3,5,7,0,1]
输出: 8 \texttt{8} 8
解释: n   =   9 \texttt{n = 9} n = 9,因为有 9 \texttt{9} 9 个数字,所以所有的数字都在范围 [0,9] \texttt{[0,9]} [0,9] 内。 8 \texttt{8} 8 是丢失的数字,因为它没有出现在 nums \texttt{nums} nums 中。

数据范围

  • n = nums.length \texttt{n} = \texttt{nums.length} n=nums.length
  • 1 ≤ n ≤ 10 4 \texttt{1} \le \texttt{n} \le \texttt{10}^\texttt{4} 1n104
  • 0 ≤ nums[i] ≤ n \texttt{0} \le \texttt{nums[i]} \le \texttt{n} 0nums[i]n
  • nums \texttt{nums} nums 中的所有数字各不相同

进阶

你能否实现 O(n) \texttt{O(n)} O(n) 时间复杂度和 O(1) \texttt{O(1)} O(1) 空间复杂度的解法?

解法一

思路和算法

首先将数组 nums \textit{nums} nums 升序排序。如果数组中没有数字丢失,则排序后的数组中,每个下标处的元素值都等于下标值。

假设丢失的数字是 x x x 0 ≤ x ≤ n 0 \le x \le n 0xn,则排序后的数组中,对于任意 0 ≤ i < x 0 \le i < x 0i<x 都有 nums [ i ] = i \textit{nums}[i] = i nums[i]=i。分别考虑 x < n x < n x<n x = n x = n x=n 的两种情况:

  • 如果 x < n x < n x<n,则 nums [ x ] ≠ x \textit{nums}[x] \ne x nums[x]=x x x x 是第一个元素值不等于下标值的下标;

  • 如果 x = n x = n x=n,则数组中的所有下标处的元素值都等于下标值。

因此只要遍历排序后的数组即可得到丢失的数字。遍历过程中,第一个元素值不等于下标值的下标即为丢失的数字。如果遍历结束之后没有发现元素值不等于下标值的下标,则范围 [ 0 , n − 1 ] [0, n - 1] [0,n1] 内的数字都不丢失,丢失的数字是 n n n

代码

class Solution {
    public int missingNumber(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            if (nums[i] != i) {
                return i;
            }
        }
        return n;
    }
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n 是数组 nums \textit{nums} nums 的长度。排序需要 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的时间,遍历数组需要 O ( n ) O(n) O(n) 的时间,总时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)

  • 空间复杂度: O ( log ⁡ n ) O(\log n) O(logn),其中 n n n 是数组 nums \textit{nums} nums 的长度。排序需要 O ( log ⁡ n ) O(\log n) O(logn) 的递归调用栈空间。

解法二

思路和算法

为了找到丢失的数字,可以使用哈希集合存储数组中的数字,然后寻找不在哈希集合中的数字。

由于所有的数字都在范围 [ 0 , n ] [0, n] [0,n] 内,因此依次遍历该范围的数字,判断每个数字是否在哈希集合中。具体做法是,首先遍历范围 [ 0 , n − 1 ] [0, n - 1] [0,n1] 内的每个数字,如果遇到不在哈希集合中的数字,则该数字是丢失的数字,返回该数字。如果范围 [ 0 , n − 1 ] [0, n - 1] [0,n1] 内的每个数字都在哈希集合中,则丢失的数字是 n n n,返回 n n n

代码

class Solution {
    public int missingNumber(int[] nums) {
        Set<Integer> set = new HashSet<Integer>();
        for (int num : nums) {
            set.add(num);
        }
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            if (!set.contains(i)) {
                return i;
            }
        }
        return n;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums 一次将数组中的所有数字加入哈希集合,然后需要遍历范围 [ 0 , n ] [0, n] [0,n] 内的每个数字寻找丢失的数字。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。哈希集合需要 O ( n ) O(n) O(n) 的空间。

解法三

思路和算法

解法二创建哈希集合需要 O ( n ) O(n) O(n) 的空间,如果可以原地哈希,则可以将空间复杂度降低到 O ( 1 ) O(1) O(1)

具体做法是,对于数组 nums \textit{nums} nums 的每个下标 i i i,如果 nums [ i ] < n \textit{nums}[i] < n nums[i]<n nums [ i ] ≠ i \textit{nums}[i] \ne i nums[i]=i,则将下标 nums [ i ] \textit{nums}[i] nums[i] 与下标 i i i 处的元素交换,交换之后 nums [ i ] \textit{nums}[i] nums[i] 的元素值等于下标值,重复该交换过程直到 nums [ i ] = n \textit{nums}[i] = n nums[i]=n nums [ i ] = i \textit{nums}[i] = i nums[i]=i,此时下标 i i i 处的元素无法继续交换。

当每个下标的交换操作都结束之后,遍历数组 nums \textit{nums} nums 寻找丢失的数字。遍历过程中,第一个元素值不等于下标值的下标即为丢失的数字。如果遍历结束之后没有发现元素值不等于下标值的下标,则范围 [ 0 , n − 1 ] [0, n - 1] [0,n1] 内的数字都不丢失,丢失的数字是 n n n

代码

class Solution {
    public int missingNumber(int[] nums) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            while (nums[i] < n && nums[i] != i) {
                swap(nums, nums[i], i);
            }
        }
        for (int i = 0; i < n; i++) {
            if (nums[i] != i) {
                return i;
            }
        }
        return n;
    }

    public void swap(int[] nums, int index1, int index2) {
        int temp = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = temp;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums 一次,每次会将一个元素交换到正确的下标位置,因此每个元素的访问次数是常数次,时间复杂度是 O ( n ) O(n) O(n)

  • 空间复杂度: O ( 1 ) O(1) O(1)

解法四

思路和算法

由于数组 nums \textit{nums} nums 中只有一个元素丢失,因此只要得到范围 [ 0 , n ] [0, n] [0,n] 内的所有整数之和以及数组 nums \textit{nums} nums 的所有元素之和,两者相减即可得到丢失的数字。

根据高斯公式可知,范围 [ 0 , n ] [0, n] [0,n] 内的所有整数之和是 n ( n + 1 ) 2 \dfrac{n(n + 1)}{2} 2n(n+1),因此可以在 O ( 1 ) O(1) O(1) 的时间内得到范围 [ 0 , n ] [0, n] [0,n] 内的所有整数之和。

代码

class Solution {
    public int missingNumber(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        int n = nums.length;
        return n * (n + 1) / 2 - sum;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums 一次计算数组元素之和。

  • 空间复杂度: O ( 1 ) O(1) O(1)

解法五

预备知识

该解法涉及到位运算中的按位异或运算。用 ⊕ \oplus 表示按位异或运算符,按位异或运算具有以下性质。

  • 交换律:对于任意整数 a a a b b b,有 a ⊕ b = b ⊕ a a \oplus b = b \oplus a ab=ba

  • 结合律:对于任意整数 a a a b b b c c c,有 ( a ⊕ b ) ⊕ c = a ⊕ ( b ⊕ c ) (a \oplus b) \oplus c = a \oplus (b \oplus c) (ab)c=a(bc)

  • 归零律:对于任意整数 a a a,有 a ⊕ a = 0 a \oplus a = 0 aa=0

  • 恒等律:对于任意整数 a a a,有 0 ⊕ a = a ⊕ 0 = a 0 \oplus a = a \oplus 0 = a 0a=a0=a

思路和算法

数组 nums \textit{nums} nums 中有 n n n 个整数,如果在这 n n n 个整数后面添加范围 [ 0 , n ] [0, n] [0,n] 内的每个整数各一个,则添加 n + 1 n + 1 n+1 个整数,共有 2 n + 1 2n + 1 2n+1 个整数。这 2 n + 1 2n + 1 2n+1 个整数中,未丢失的数字在前 n n n 个整数中以及在后 n + 1 n + 1 n+1 个整数中各出现一次,丢失的数字只在后 n + 1 n + 1 n+1 个整数中出现一次,因此每个未丢失的数字出现两次,丢失的数字出现一次。

利用按位异或运算的性质,对上述 2 n + 1 2n + 1 2n+1 个整数计算按位异或的结果。所有出现两次的数字经过按位异或运算之后的结果是 0 0 0,只有出现一次的数字在按位异或运算之后保留,因此上述 2 n + 1 2n + 1 2n+1 个整数的按位异或的结果即为丢失的数字。

代码

class Solution {
    public int missingNumber(int[] nums) {
        int xor = 0;
        for (int num : nums) {
            xor ^= num;
        }
        int n = nums.length;
        for (int i = 0; i <= n; i++) {
            xor ^= i;
        }
        return xor;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums 一次以及遍历范围 [ 0 , n ] [0, n] [0,n] 内的所有整数计算按位异或的结果。

  • 空间复杂度: O ( 1 ) O(1) O(1)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值