思路
二分法。
- 答案初始值为0,当前位i为最高位,比如32;
- 对当前文件进行处理:根据当前第i位为0、为1,将文件一分为二;
- 比较这两组数哪个较多,如果为1较多则答案第i为置0,为1较多则答案第i位置0;
- 接着继续转向较少的那组数;
- 重复以上步骤2-3
正确性
- 32位整数的范围是0到232(大于40亿),则对于一个随机40亿个整数来说,有232-40亿=294,967,296,约3亿个数不存在,即问题要在这3亿数内找一个出来。
- 再看0到232这个范围,考虑到32位太长就以十六进制代替二进制进行表示:0x0000,0000~0xffff,ffff。如果全部包括232个数,则可以看到0和1的个数是相等的,即各自为50%的出现概率。
- 假设32位整数A和32位整数B在第i个二进制位处分别是0和1,则认为A和B是i位互补数(在这里仅供说明参考用)。归纳推广,根据50%的出现概率,考虑任意一个二进制位时,都可以将232个数划分为2个互补数的集合,即A集合和B集合。如果任意一位二进制位的互补数缺失一个,那么在该位处的0,1出现概率肯定不等,缺失数所代表的二进制值(0/1)的概率肯定小于存在数所代表的二进制值(1/0)的概率,【选择较少的数是为了补全这50%的概率,也就是较少的数等于缺失数的原因】。所以来看算法实现,程序的作者依次比较每个二进制位时选择较少数作为结果是有理论依据了。当然,为了更好的快速收敛提高效率,每次迭代都在缩小待搜索整数的范围,即用alen来控制二进制位数。
代码示例
int split(int* a, int* b, int*c, int alen, int bit)
{
int biter, citer, i;
int v=0, re = 0, *t;
while(bit--){
v = (1 << bit);
for(i=biter=citer=0; i < alen; i++) {
if(a[i] & (1<<bit)) {
b[biter++] = a[i];
} else {
c[citer++] = a[i];
}
}
if(biter <= citer) {
re += v;
t = a;
a = b;
b = t;
alen = biter;
} else {
t = c;
c = a;
a = t;
alen = citer;
}
}
return re;
}