题目描述
给定一个包含 0, 1, 2, ..., n
中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。
示例 1:
输入: [3,0,1] 输出: 2
示例 2:
输入: [9,6,4,2,3,5,7,0,1] 输出: 8
说明:
你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现?
题解1
看到这个题的第一个思路是标记法,之前在那好像看见过,具体出处忘了了。
题意分析:题中n个元素取值为[0, n],且n个元素都不会重复,问题是让我们找到非负的元素。我们可以利用元素本身都是非负性,我们可以将存在的元素num,其下标对应的元素变成负的(0的负数仍为其本身,需特殊处理),即nums[abs[num]] = -nums[abs[num]]
代码1
/*
用value的正负来做标记,nums[idx]为负表示idx出现在序列中
*/
class Solution {
public:
int missingNumber(vector<int>& nums) {
int len = nums.size();
int res = -1, tmp = 0;
bool flag = false;
nums.push_back(1);//添加一个元素,避免标记时越界
for(int i = 0; i < len; ++i){
nums[abs(nums[i])] = - nums[abs(nums[i])];
}
for(int i = 0; i < len + 1; ++i){
if(nums[i] > 0){
res = i;
flag = true;
break;
}
if(nums[i] == 0){//对值为0的下标进行标记,用于处理特殊情况
tmp = i;
}
}
if(!flag){//当所有元素都非正时,表明缺失的下标的元素值为0
res = tmp;
}
return res;
}
};
参考LeetCode题解,题解2是求和的方法,题解3是异或的方法。
题解2
求和法。从[0, n]中取n个元素,总和为 ,减去选取元素之和,得到的就是缺失的元素。
/*
求和法
求和,再减去所选元素之和
*/
class Solution {
public:
int missingNumber(vector<int>& nums) {
int len = nums.size();
int res = -1, sum = len * (len + 1) / 2;
for(auto num:nums){
sum -= num;
}
res = sum;
return res;
}
};
正如该解法下的评论所言
高斯求和明显有溢出风险
所以有人对求和法进行了改进
直觉首先想到了数学方法,只需要遍历一遍数组,在把0-n这n个自然数全加起来的同时也减去nums[i],这样不但效率高,也防止了数据溢出
/*
求和法
求和,再减去所选元素之和
*/
class Solution {
public:
int missingNumber(vector<int>& nums) {
int len = nums.size();
int res = -1, sum = 0;
for(int i = 1; i <= len; ++i){//边求和边减,防止溢出
sum = sum + i - nums[i - 1];
}
res = sum;
return res;
}
};
题解3
既然可以先求和再减,那么只要先编码
再解码
,采用两个互逆的操作,便可以找到缺失的元素。乘法和除法是两个互逆的操作,但用到这题溢出的可能性更大。异或也是个可逆操作。如
异或这个很好的性质,有很大的作用,比如说硬盘的数据恢复,参照CCF 201903-3损坏的RAID5,这里用来寻找缺失的数字,类似于数据恢复。异或作为位运算,相比求和的方法,速度快的多。
/*
异或法
*/
class Solution {
public:
int missingNumber(vector<int>& nums) {
int len = nums.size();
int res = -1, sum = 0;
for(int i = 1; i <= len; ++i){
sum = sum ^ i ^ nums[i - 1];
}
res = sum;
return res;
}
};