前言
(1)因为为了参加ARTS 挑战打卡,要求每周刷一道leetcode。作为一名嵌入式工程师,虽然也是搞软件的,但是对leetcode刷题方面强调的很少。
(2)毕竟还是做软件开发的,虽然嵌入式方向对这个强调比较少,还是刷一点简单的题目培养一下自己的思维。
(3)这到简单题做了我半个小时,最终击败5%的人。(苦笑)
(4)原题链接 : https://leetcode.cn/problems/missing-number-lcci/
题目
(1)数组nums包含从0到n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗?
(2)示例1
输入:[3,0,1] 输出:2
(3)示例2
输入:[9,6,4,2,3,5,7,0,1] 输出:8
思路1
(1)最开始的思路,毫无疑问采用冒泡排序。将这个无序的数组变成有序数列。然后将这个数组相邻两项相减,如果不为1,就返回中间这个数据。
(2)当时我是这样写的,最后没想到,提交上去,他给我玩骚操作,数组只有一个[0],打我一个措手不及,最终输出结果要求是1,而我这里是-1。
int missingNumber(int* nums, int numsSize){ int x,y,tmp; for(x=0 ; x<numsSize ; x++) { for(y=1 ; y<numsSize-x ; y++) { if(nums[y] < nums[y-1]) { tmp = nums[y]; nums[y] = nums[y-1]; nums[y-1] = tmp; } } } for(x=1 ; x<numsSize ; x++) { if((nums[x] - nums[x-1]) != 1) { return nums[x] - 1; } } return -1; }
(3)于是我更改策略,最后返回值是1。
int missingNumber(int* nums, int numsSize){ int x,y,tmp; for(x=0 ; x<numsSize ; x++) { //... } for(x=1 ; x<numsSize ; x++) { //... } return 1; }
(4)没想到,他又给我整出来了一波骚操作,输入数组为[1]。
(5)于是我就想如果数组只有一个值,就判断这个值是不是0。如果不是0就返回0,如果数组第一个值是0就返回1。
int missingNumber(int* nums, int numsSize){ int x,y,tmp; for(x=0 ; x<numsSize ; x++) { //... } if(numsSize == 1 && nums[0] !=0) return 0; for(x=1 ; x<numsSize ; x++) { //... } return 1; }
(6)然后没想到这串代码又出问题了,他给我[0,1]数组,最终返回的是一个1,但是要求的返回值是2。于是我知道,如果这个数组中间没有缺失整数,那么久就返回不存在与这个数组中,并且比他大的一个值。
(7)于是我继续调整,如果这个数组没有问题,就返回刚好比这个数组大1的值。
int missingNumber(int* nums, int numsSize){ int x,y,tmp; for(x=0 ; x<numsSize ; x++) { //... } //... for(x=1 ; x<numsSize ; x++) { //... } return nums[x] + 1; }
(8)之后就发现报错了。AddressSanitizer: heap-buffer-overflow on address
(9)这里涉及到一个C语言的小知识。首先执行表达式1,再执行判断表达式2,如果符合,则执行表达式4,否则,停止执行,最后执行表达式3。一开始我认为如果判断表达式2没有成功,表示式3和4都不会执行。现在才发现最基础的知识都搞错了。
for(表达式1;表达式2;表达式3){ 表达式4; }
(10)因此,我又做了一次调整。让这个for循环的判断条件都改变一下。
int missingNumber(int* nums, int numsSize){ int x,y,tmp; for(x=0 ; x<numsSize ; x++) { //... } //.. for(x=0 ; x<numsSize-1 ; x++) { if((nums[x+1] - nums[x]) != 1) { return nums[x+1] - 1; } } return nums[x]+1; }
(11)不出意外,又报错了。[1,2]的数组,实际上应该是要返回0的,但是我这里返回的是3。
(12)因此我做出了最后一次调整,只要排序之后的数组第一个值不是0,那么就会返回0。
int missingNumber(int* nums, int numsSize){ int x,y,tmp; for(x=0 ; x<numsSize ; x++) { //... } if( nums[0] != 0) return 0; for(x=0 ; x<numsSize-1 ; x++) { //... } return nums[x]+1; }
思路2
(1)虽然上面的代码成功运行了。但是通过复杂度计算,发现上面这个复杂度是O(N^2)。
(2)此时我就感觉方法不行,后面看了别人的思路之后,发现可以将所有的0到N加到一起为ret1,而数组的值都加起来, 是ret2。最终让ret1-ret2。
int missingNumber(int* nums, int numsSize){ long int i,nums_sum=0,need_sum=0; for(i=0 ; i<numsSize ; i++) { nums_sum += nums[i]; need_sum += i; } return need_sum - nums_sum; }
(3)后面运行之后发现,结果永远是1。
(4)经过简单的思考之后发现,如果你是缺少了某一项,那么need_sum要比nums_sum多加一个数字,所以for结束之后,need_sum还需要进行一次加法。
int missingNumber(int* nums, int numsSize){ int i; long int nums_sum=0,need_sum=0; for(i=0 ; i<numsSize ; i++) { nums_sum += nums[i]; need_sum += i; } need_sum += i; return need_sum - nums_sum; }
思路3
(1)正常来说,上面的方法已经很好了。但是我们是不是再换一个思路呢?
(2)我们需要知道C语言有一个东西,叫做异或^。如果一个数,被偶数次异或 ^,那么就会被消除。
(3)因此,我们可以让x进行异或操作,最终异或剩下来的值就是被忽略的值。
(4)这个方法相比较上面那个,节约了一点点空间,从时间复杂度上并没有提升。
int missingNumber(int* nums, int numsSize){ int x=0,i; for(i=0 ; i<numsSize ; i++) { x ^= i; x ^= nums[i]; } x ^= i; return x; }
总结
被偶数次异或 ^,相当于什么都没有发生。我们要积极利用这一条规则。