有一个数组data[n-1]存储了1~n中的n-1个数,问data中缺少的数字是多少【每日一题】
2015-04-22 14:34
155 查看
今天参加面试的时候面试官问了这个问题,当时很快速的想出了三种解决方案,当时还自我感觉挺好,回来后跟师兄一说,师兄说这个你没想出最好的方案来。: (
该算法中,排序的时间复杂度为O(nlogn),然后再遍历一次,因此综合时间复杂度是O(nlogn),空间复杂度O(c)。
该算法中需要遍历一次目标数组,遍历两次exists数组(初始化+查看数组值),时间复杂度O(n),空间复杂度O(n)
该算法简单易懂,且计算1+2+…+n可直接用公式n*(n+1)/2,然后减去data中的每个数需要遍历data,时间复杂度O(n),空间复杂度O(c)。
“如果我的n很大呢?”
当时我没明白他的意思,我还以为他时间复杂度,我说,n很大也没事儿啊,一次遍历O(n)。现在想想真的是太天真了。面试官想说的是:
当n很大的时候,n*(n+1)/2是有可能溢出的,这种情况下加减法的这种解决方案是有问题的
那么,该如何解决或避免这个问题呢?或许有人说可以换用范围更大的数据类型,但是这不是面试官最想听的答案,答案请接着往下看。
其次,这个方案是下面这个题的变种(参考/article/1425252.html)
有2n+1个数,其中有2n个数出现过两次,找出其中只出现一次的数
来看我们的原题,data[n-1]中存了n-1个数,每个数都是1~n之间的数,且没有重复,那么我们如果用这个n-1个数再加上1~n就变成了2n-1个数,且其中只有一个数出现了一次,其他均出现了两次。这就变成了上面这个题了。
我们来论证一下这个算法的正确性:
0 ^ 1 = 1, 1 ^ 0 = 1, 0 ^ 0 = 0, 1 ^ 1 = 0
对于任意整数n,n ^ 0 = n, n ^ n = 0
(1)当n与0异或时,由于0的所有二进制位均为0,因此,n的二进制位中为1的与0相应位的二进制位0异或结果为1,n的二进制位中为0的与0相应位的二进制位0异或结果为0,因此异或后的结果与n本身完全相同;(2)当n与n异或时,由于其二进制位完全相同,而根据1中0 ^ 0 = 0, 1 ^ 1 = 0,n ^ n结果的所有位均为0,所以结果为0。
异或运算满足交换结合律 a ^ b ^ c = a ^ c ^ b.
其实我们可以将所有的abc均看做二进制形式,其结果可以看做是如下运算:
00000000 00000000 00000000 00000010 a = 2
^
00000000 00000000 00000000 00000001 b = 1
^
00000000 00000000 00000000 00000100 c = 4
00000000 00000000 00000000 00000111 result = 7
即所有运算数的每一位分别异或,因此不论运算顺序如何,结果都相同。
结论
综合1、2、3,然后再根据我们的数据的特点,有2n-1个数,其中有n-1个数出现了两次,只有一个数出现了1次,那么我们将所有的2n-1个数进行异或时,可以看成如下过程,对于出现了两次的元素,x ^ x = 0,然后是n-1个0和剩余的那个只出现了一次的y进行异或,n-1个0异或的结果还是0,最后再与y异或结果是y,y就是我们要找的缺失的元素,因此上述算法是正确的。
这个算法,需要将所有元素做异或运算,时间复杂度O(n),空间复杂度O(c),而且不会有溢出的问题,这是面试官最喜欢的答案了。
代码中稍微做了一点点优化,在前n-1次循环的时候执行两次异或,分别异或data[i]和i+1,这样,代码循环只执行了n-1次,比两个for循环少一次循环。
嗯,面试官最喜欢的就是最后这个答案了。悔不早知 : (
// 个人学习记录,若有错误请指正,大神勿喷
// sfg1991@163.com
// 2015-04-22
问题描述
有一个数组data,大小是n-1,其中存储的是1~n中的数字,不重复,即1~n中只有一个数字不在该数组内,找出该数字。基础方法
先把我想出的几种方法例举一下吧。排序 时间复杂度O(nlogn),空间复杂度O(c)
算法描述
先将data中的元素排序,然后从头开始遍历,如果遍历中发现元素有间断,则为确实的数据。该算法中,排序的时间复杂度为O(nlogn),然后再遍历一次,因此综合时间复杂度是O(nlogn),空间复杂度O(c)。
代码
#include <stdio.h> #include <stdlib.h> int cmp (const void *a, const void *b) { return *(int *)a - *(int *)b; } // 找到不存在的元素 // data: 目标数组 // n: 数组中元素的最大可能值 // data数组的大小为n-1 int findLost(int data[], int n) { // 快排 qsort(data, n - 1, sizeof(int), cmp); int i; for (i = 0; i < n; ++i) { if (data[i] != i + 1) // 如果第i个数不等于i+1,则返回i+1 return i + 1; } // 程序执行到这里说明前面1~n-1个数都不缺,缺第n个数 return n; } int main(void) { int data[8] = {1, 2, 3, 5, 8, 6, 7, 4}; printf("%d\n", findLost(data, 9)); return 0; }
额外空间记录 时间复杂度O(n),空间复杂度O(n)
算法描述
用一个大小为n的数组exists,标记目标数组中的元素是否存在,遍历目标数组,修改exists相应位置的值,最后遍历exists找到不存在的元素。该算法中需要遍历一次目标数组,遍历两次exists数组(初始化+查看数组值),时间复杂度O(n),空间复杂度O(n)
代码
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> // 找到不存在的元素 // data: 目标数组 // n: 数组中元素的最大可能值 // data数组的大小为n-1 int findLost(int data[], int n) { bool *exists = malloc(sizeof(bool) * n); int i; // 初始化 for (i = 0; i < n; ++i) *(exists + i) = false; // 遍历data,修改标志位 for (i = 0; i < n - 1; ++i) *(exists + data[i] - 1) = true; // 遍历exists,找到不存在的元素 for (i = 0; i < n; ++i) if (!*(exists + i)) { free(exists); return i + 1; } // 对于有效数据,程序不会执行到这里 free(exists); return 0; } int main(void) { int data[8] = {1, 8, 3, 5, 9, 6, 7, 4}; printf("%d\n", findLost(data, 9)); return 0; }
加减法 时间复杂度O(n),空间复杂度O(c)
算法描述
计算1+2+…+n,用该值减去data中的每个数,差就是缺少的数。该算法简单易懂,且计算1+2+…+n可直接用公式n*(n+1)/2,然后减去data中的每个数需要遍历data,时间复杂度O(n),空间复杂度O(c)。
代码
#include <stdio.h> // 找到不存在的元素 // data: 目标数组 // n: 数组中元素的最大可能值 // data数组的大小为n-1 int findLost(int data[], int n) { int sum = n * (n + 1) / 2; int i; for (i = 0; i < n - 1; ++i) sum -= data[i]; return sum; } int main(void) { int data[8] = {1, 8, 3, 5, 9, 6, 7, 4}; printf("%d\n", findLost(data, 9)); return 0; }
基础方法总结
对于上述三种方法,第一种时间复杂度不好,第二种空间复杂度不好,第三种时间复杂度和空间复杂度都不错,但是有一个隐藏的缺陷。当时面试官问我:“如果我的n很大呢?”
当时我没明白他的意思,我还以为他时间复杂度,我说,n很大也没事儿啊,一次遍历O(n)。现在想想真的是太天真了。面试官想说的是:
当n很大的时候,n*(n+1)/2是有可能溢出的,这种情况下加减法的这种解决方案是有问题的
那么,该如何解决或避免这个问题呢?或许有人说可以换用范围更大的数据类型,但是这不是面试官最想听的答案,答案请接着往下看。
高级解决方案
首先,这个方案使用的位运算中的异或(^),a^b当a和b不相等时为1,相等时为0。其次,这个方案是下面这个题的变种(参考/article/1425252.html)
有2n+1个数,其中有2n个数出现过两次,找出其中只出现一次的数
来看我们的原题,data[n-1]中存了n-1个数,每个数都是1~n之间的数,且没有重复,那么我们如果用这个n-1个数再加上1~n就变成了2n-1个数,且其中只有一个数出现了一次,其他均出现了两次。这就变成了上面这个题了。
算法描述
将data中的所有元素进行异或运算,然后再将结果与1~n每个元素依次异或,最后得到的结果就是缺少的元素(只出现了一次的元素)。我们来论证一下这个算法的正确性:
0 ^ 1 = 1, 1 ^ 0 = 1, 0 ^ 0 = 0, 1 ^ 1 = 0
对于任意整数n,n ^ 0 = n, n ^ n = 0
(1)当n与0异或时,由于0的所有二进制位均为0,因此,n的二进制位中为1的与0相应位的二进制位0异或结果为1,n的二进制位中为0的与0相应位的二进制位0异或结果为0,因此异或后的结果与n本身完全相同;(2)当n与n异或时,由于其二进制位完全相同,而根据1中0 ^ 0 = 0, 1 ^ 1 = 0,n ^ n结果的所有位均为0,所以结果为0。
异或运算满足交换结合律 a ^ b ^ c = a ^ c ^ b.
其实我们可以将所有的abc均看做二进制形式,其结果可以看做是如下运算:
00000000 00000000 00000000 00000010 a = 2
^
00000000 00000000 00000000 00000001 b = 1
^
00000000 00000000 00000000 00000100 c = 4
00000000 00000000 00000000 00000111 result = 7
即所有运算数的每一位分别异或,因此不论运算顺序如何,结果都相同。
结论
综合1、2、3,然后再根据我们的数据的特点,有2n-1个数,其中有n-1个数出现了两次,只有一个数出现了1次,那么我们将所有的2n-1个数进行异或时,可以看成如下过程,对于出现了两次的元素,x ^ x = 0,然后是n-1个0和剩余的那个只出现了一次的y进行异或,n-1个0异或的结果还是0,最后再与y异或结果是y,y就是我们要找的缺失的元素,因此上述算法是正确的。
这个算法,需要将所有元素做异或运算,时间复杂度O(n),空间复杂度O(c),而且不会有溢出的问题,这是面试官最喜欢的答案了。
代码
#include <stdio.h> // 找到不存在的元素 // data: 目标数组 // n: 数组中元素的最大可能值 // data数组的大小为n-1 int findLost(int data[], int n) { int result = 0; int i; for (i = 0; i < n - 1; ++i) { result ^= data[i]; result ^= (i + 1); } result ^= n; return result; } int main(void) { int data[8] = {1, 8, 3, 5, 9, 6, 7, 4}; printf("%d\n", findLost(data, 9)); return 0; }
代码中稍微做了一点点优化,在前n-1次循环的时候执行两次异或,分别异或data[i]和i+1,这样,代码循环只执行了n-1次,比两个for循环少一次循环。
嗯,面试官最喜欢的就是最后这个答案了。悔不早知 : (
// 个人学习记录,若有错误请指正,大神勿喷
// sfg1991@163.com
// 2015-04-22
相关文章推荐
- 已知一个数组int[98],该数组里面存储了0~99共100个数字中的98个,数字不重复,请用算法算出0~99中缺少的2个数字是哪两个?
- [每日练习]一个数组中有一个数字只出现一次,其他数字都出现了偶数次。请找出一个只出现一次的数字
- 对于一个数字,我们可以直观的了解到它所含有的数字,例如: 3243中包含1个2、2个3、1个4。 那么,给你一个数n,求从1到n之间的所有这些数字中,总共包含多少个m。
- 统计一个数字在排序数组中出现的次数。 数组是排序的,只需要找到第一个K和最后一个K就可以知道有多少个,
- 习题 8.16 输入一个字符串,内有数字和非数字字符,将其中连续的数字作为一个整数,依次存放到一数组a中。统计共有多少个整数,并输出这些数。
- 数据库某个字段是1,2,3形式存储,传入一个数字,如果这个数组有这个值,修改这个字段,去掉和传入一样的数
- 在有序递增数组中查找一个缺少的数字
- 数组a[N],存放了1至N-1个数,其中某个数重复一次。写一个函数,找出被重复的数字.时间复杂度必须为o(N)
- 数组a[N],存放了1至N-1个数,其中某个数重复一次。写一个函数,找出被重复的数字.时间复杂度必须为o(N)函数原型:
- 在按顺序递增的数组中查找缺少的某一个数字
- (思科笔试)数组a[N],存放了1至N-1个数,其中某个数重复一次。写一个函数,找出被重复的数字.时间复杂度必须为o(N)函数原型:
- 输入一个字符串,内有数字和非数字字符,将其中连续的数字作为一个整数,依次存放到一数组a中。统计共有多少个整数,并输出这些数。
- 给定一个包含从0,1,2,...,n中取出的n个不同数字的数组,找到数组中缺少的数字。
- 把一个数字的每一位存储到一个数组中
- 问题描述如下: 有2.5亿个整数(这2.5亿个整数存储在一个数组里面,至于数组是放在外存还是内存,没有进一步具体说明); 要求找出这2.5亿个数字里面,不重复的数字的个数; 另外,可用的内存限定为600M; 要求算法尽量高效,最优;
- [每日练习]Amazon面试题:数组有N-2个数字,数字的范围为1 ... N,没有重复的元素,要求打印缺少的2个数字,不可以用额外的空间
- 有一个数组,存储的元素为1到10000000的任意数,在其中查找出一个重复的数字
- 编写一个程序,最多将10个donation值读入到一个double数组中。程序遇到非数字输入时将结束输入,并报告这些数字的平均值以及数组中有多少个数字大于平均值。
- 数组a[N],存放了1至N-1个数,其中某个数重复一次。写一个函数,找出被重复的数字.时间复杂度必须为o(N)函数原型:
- 一个数组中只有两个数字是出现一次,其他所有数字都出现了两次。