消失的数字
给定一个包含 [0, n]
中 n
个数的数组 nums
,找出 [0, n]
这个范围内没有出现在数组中的那个数。
示例 1:
输入:nums = [3,0,1] 输出:2 解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 2:
输入:nums = [0,1] 输出:2 解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。
示例 3:
输入:nums = [9,6,4,2,3,5,7,0,1] 输出:8 解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。
示例 4:
输入:nums = [0] 输出:1 解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。
提示:
n == nums.length
1 <= n <= 104
0 <= nums[i] <= n
nums
中的所有数字都 独一无二
进阶:你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?
解题思路
由题意可知,长度为n的数组中,有n个不同的数字;而题目中说的,在[0, n]范围内,总共有n+1个数字,我们需要找到缺少的那一个数字并返回。
我们尝试用简单一点儿的方法找到缺少的数字:
- 我们可以初始化一个长度为n+1的boolean型数组m,这样它的下标范围为[0, n],正好就是全部的数字范围,然后我们先遍历题目中提供的长度为n的数组nums,把数组nums的值作为数组m的下标,并设置数组m的元素值为true;接着我们再遍历一遍数组m,找到元素值为false的下标值,这个下标即缺少的数字。
- 我们可以对数组nums排序,使其元素值升序排列;然后我们可以用二分法,比较数组nums的下标和对应的元素值,我们先找下标为n/2处的元素,如果下标等于对应的元素值,说明在[0, n/2]范围内的下标和元素值都相等,则我们用二分法在[n/2 + 1, n - 1]范围内查找;如果下标小于元素值,则我们用二分法在[0, n/2 -1]范围内查找。按照这个思路,直到我们找到确实的数字即可。
在上面两种方法中,其中方法一是线性时间复杂度,但是也用到了线性的额外空间;而方法二,只用到了常数的额外空间,但是时间复杂度超过了线性。
如何实现线性时间复杂度、又仅使用额外常数空间的呢?这里提供两种解法:
- 解法一:我们可以用算出0到n之和,然后减去nums的元素值,得到的差即为缺少的数字
- 解法二:我们可以用到一种运算方式:异或运算。
两个相同的数字之间异或,会得到0;而0与任何一个数字之间异或,会得到这个数字。也就是说,重复的数字异或的结果是0,而唯一的数字异或的结果就是这个数字,所以我们可以运用异或运算,找到缺少的数字。
我们来分析一下,数字nums的长度为n,它的下标范围为[0, n - 1],元素值的范围在[0, n]之间,但是只有n个数字。我们需要初始化一个变量k,然后用k分别与下标和相应的元素值做异或运算;那么变量k应该初始化为哪个值呢?我们应该初始化为n,这样我们就有了在完整的范围[0, n]之间的n+1个值,然后和范围[0, n]之间的n个元素值做异或运算,最后就可以得到那个缺少的数字了。
具体代码如下:
class Solution {
public int missingNumber(int[] nums) {
int loss = nums.length;
for(int i = 0; i < nums.length; i++){
loss ^= i ^ nums[i];
}
return loss;
}
}
复杂度分析
- 时间复杂度:O(n),需要遍历一遍数组nums。
- 空间复杂度:O(1),使用了常量的额外空间。