题目:
给定一个大小为n的数组A[0..n-1],数组中包含0..n中的n个数,其中有一个数丢失,没有在数组中。现在假设数组中的数据只有一个fetch(i)操作,用于获取树脂第i为的bit位。要求给定一个O(n)的算法,找出缺失的那个数。
分析:
对于这个题目,由于我们一次只能获取数值的一个位信息。所以我们无法利用常规的方法,比如将0+1+2+..+n的和值减去数组A中所有的元素的和,或是利用0^1^2^..^n^A[0]^A[1]^..^A[n-1]这种用异或的位操作来求解。现在我们对数值的位进行仔细的分析,假设此处的n为5,我们观察从0到5的最低为的bit值为:010101,此时若我们缺少的数值的最低位为0,则可以看到数组中最低位0的个数count(0) < count(1);若缺失的数值的最低位为1,则count(1)<count(0).下面再看看n为6的情况,此时0到6的最低位bit值为:0101010,若缺失的数值最低位为0,则count(0)=count(1);否则count(1)<count(0)。
事实上,当n为奇数时并且缺失的数值的最低位为0,则count(0)<count(1);当n为奇数且缺失的数值的最低位为1时,count(1)<count(0);对于n为偶数时,缺失0则count(0)<count(1),缺失1,则count(1)<count(0)。综上,当当前为缺失的为1,则count(1)<count(0),当缺失的为0,则count(0)<=count(1)。因此我们可以按位来确定缺失的数值每一位的值是多少来找出这个缺失的数值。
过程如下:
统计最低有效位(假设为第i位)的0和1的个数,根据它们的值确定缺失的数值的第i位为0还是1,若为0,则排除数组中所有最低有效位为1的数值,否则排除最低有效位为0的所有数值,并统计剩下的数值中第i+1位的信息,确定缺失数值的第i+1位的取值。按这个过程依次求出缺失的数值的每一位的的取值。
代码:
int findMissing(int *A, size_t n)
{
if(A == NULL || n < 0) return -1;
int max_column = 0;
while(n > (0x1 << max_column)) max_column++;
return _findMissing(A, n, max_column, 0);
}
int _findMissing(int *A, size_t size, int max_column, int column)
{
if(column > max_column) return 0;
int count_0 = 0, count_1 = 0;
int *even = new int[size/2 + 1]; //当前位为0的数值
int *odd = new int[size/2 + 1]; //当前位为1的数值
for(int i = 0; i < size; i++)
{
if(fetchBit(A[i]) == 0) even[count_0++] = A[i];
else odd[count_1++] = A[i];
}//for
int res;
if(count_0 <= count_1) res (_findMissing(even, count_0, max_column, column+1)<<1)|0;
else res (_findMissing(odd, count_1, max_column, column+1)<<1)|1;
delete[] even;
delete[] odd;
return res;
}
复杂度:
最后,来看看这个算法的时间复杂度。首先,在第一次迭代时,需要统计的数值个数为n;在第二次迭代时,由于排除了一半的数值,所以需要统计的数值个数为n/2;一次类推,在第i次迭代时,需要统计的数值个数为n/2^i个。因此总的时间复杂度为O(n) + O(n/2) + O(n/4) + .. + O(1) = O(2n) = O(n)。再来看算法的空间复杂度,这个算法的过程跟归并排序的过程一样,因此空间复杂度也为O(n).