给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。
示例 1:
输入: [3,0,1]
输出: 2
示例 2:
输入: [9,6,4,2,3,5,7,0,1]
输出: 8
题目说明:你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现?
这道题我的思路其实并不丰富,最初想的就是自己建一个比传到函数中的vector长度大1的vector,然后把序列中的数按数字存到相应的下标。最后判断是哪个没有存进去,返回就OK。但是提交之后,发现效率不是很高,耗时28ms。接着想用关联容器来实现,但是也没有找到合适的方法。然后去看题解去了,发现了一位大神写了四种方法,真的是让我顶礼膜拜。推荐你们看一下:https://leetcode-cn.com/problems/missing-number/solution/que-shi-shu-zi-by-gpe3dbjds1/
特别是他写的异或和等差数列的方法,真的是很牛逼。位运算本身就很快,所以异或这个方法真的绝了,确实不是一般人能想到的。
先来看一下我的方法:
class Solution {
public:
int missingNumber(vector<int>& nums) {
if (nums.empty())
return 0;
int size = nums.size();
vector<int> result(size+1,-1);
for (int val : nums)
{
result[val] = 1;
}
for (int i = 0; i < result.size(); ++i)
{
if (result[i] != 1)
{
return i;
}
}
return 0;
}
};
这个方法确实不好,虽然时间上是28ms,但是却额外浪费了空间。
所以可以看看以下方法:
1、排序
题目中说的很清楚,算上缺少的数,所有的数与下标都是一一对应的。那么n个数中缺少一个,那么剩下的数排序之后肯定有与下标不对应的,那么找到第一个不对应于的就知道是缺少了哪个数。
class Solution {
public:
int missingNumber(vector<int>& nums) {
if (nums.empty())
return 0;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); ++i)
{
if (nums[i] != i)
return i;
}
return nums.size();
}
};
这种方法执行下来是44ms。泛型算法本身的排序也很耗时间,再来一次循环遍历,时间效率上来说确实不太好。
2、异或
异或的特性也是解决这道题的一个好办法。因为加上缺少的数字,vector中的每个数和相应的下标都是相等的。那么缺少了一个数字,可是它本应对应的下标却是唯一存在的。所以循环异或就可以判断出缺少哪个数字。
class Solution {
public:
int missingNumber(vector<int>& nums) {
if (nums.empty())
return 0;
int x = nums.size();
for (int i = 0; i < nums.size(); ++i)
{
x ^= nums[i];
x ^= i;
}
return x;
}
};
异或方法执行下来是24ms
3、等差数列法
可以发现这个vector中的数据都是有序的,并且差值为1。那么利用我们高中学习过的等差数列先求得它的和,再减去vector里面的值,就可以得到缺少的数字。
等差数列求和公式:Sn = na1 + (n(n-1)/2)*d;由题可知,a1=0,d=1;
那么针对这道题的求和公式就可以演变为:Sn = n(n-1)/2;n为项数
又根据题的意思可以得知,vector中的最大数肯定是和数组大小一致的,但是却缺少一个,所以这里的n都要加上1,才能得到本应的和。
class Solution {
public:
int missingNumber(vector<int>& nums) {
if (nums.empty())
return 0;
int sum = (nums.size()*(nums.size() + 1)) / 2;
for (int i = 0; i < nums.size(); ++i)
{
sum -= nums[i];
}
return sum;
}
};
等差数列法求出来是最快的,只需16ms。