在学习完位操作后,经常会遇到一类关于查找缺失整数的问题。
第一类是给你一个数组,告诉你这些数字的范围是什么,然后让你查找这个缺失的数字(例如无序数组的范围是从1到10,不重复的9个数)。
这类问题的解决方法比较多样,第一种,因为给定了范围可以通过计算数字总和值,然后分别减去这些数字,剩下的则是缺失的数字。第二种,对这个数组进行排序,遍历整个数组,然后判断相邻的元素是否连续,如果是,那缺失的数字则在两段;如果不连续,中间缺少的则是需要查找的。
第二类还是给你一个数组,然后这些数字都出现了偶数次,只有一个数字出现了奇数次,让你查找这个奇数次的数字,例如( 1, 2, 3, 4, 1, 2, 3)。
思路:通过一种运算,让相同的元素在进行运算后可以相互消除,最后剩下的元素则是寻找的元素。这种位运算就是“异或”。通过异或,相同的数所有位都是0,而单独的那个数则是它本身。
#include<stdio.h>
int single_numble(int arr[],int size)
{
int i = 0;
int sum = 0;
for (i = 0; i < size; i++)
{
sum ^= arr[i];
}
return sum;
}
int main()
{
int arr[] = { 1, 2, 3, 4, 1, 2, 3 };
int ret = single_numble(arr, sizeof(arr) / sizeof(arr[0]));
printf("%d\n", ret);
system("pause");
}
当题目条件发生改变,这个数组的数字可能出现了多次,但还是只有一个数字出现了一次,查找这个数字(假设在这里是出现了3次)。
思路:对于整数32位,对于每一位,整个数组的数加起来去除3的余数,就是在该元素在此位上的值。
#include<stdio.h>
int single_numble(int arr[],int size)
{
int i = 0;
int j = 0;
int ret = 0;
int val = 0;
for (i = 0; i < 32; i++)
{
int count = 0; //计数器
int mask = 1 << i; //mask的第i位为1,其余的位为0
for (j = 0; j < size; j++)
{
val = arr[j] & mask;
if ((val>0) || (val < 0)) //如果该数在j位上为1,计数一次
count++;
}
if (count % 3>0) //这一位的计数除以3取余,在这里只能为1/0
ret|= mask;
}
return ret;
}
int main()
{
int arr[] = {2,2,2,4};
int ret = single_numble(arr, sizeof(arr) / sizeof(arr[0]));
printf("%d\n", ret);
system("pause");
}
总结:当一个数组有一个数恰好出现了k次,都可以用这个方法来解决。利用合适的位运算符将每一位保存,然后在找出这一位原来的数字。
第三类将难度又提升了,还是给你一个数组,里面的数字还是成对出现的,但单独出现的数字变成了两个,查找这两个数字。
思路:还是可以运用位运算的,将这个数组分成两部分,每一部分包含一个只出现一次的整数,这样子题目就和第二类差不多了。具体步骤是先对数组的每一个元素进行异或,得到的是两个数异或的结果,在这个结果中至少包含一个二进制位是1。
#include<stdio.h>
int research(int arr[],int len,int *p1,int *p2)
{
int i = 0;
int res = 0;
int pos = 0;
for (i = 0; i < len; i++) //对所有的元素进行异或
{
res ^= arr[i];
}
for (i = 0; i < len; i++) //找出一个二进制位为1的位置
{
if ((res << i) & 1 == 1)
{
pos = i;
break;
}
}
for (i = 0; i < len; i++)
{
if ((arr[i] >> pos) & 1 == 1)
{
(*p1) ^= arr[i];
}
}
(*p2) = res ^ (*p1);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 1, 3, 5, 8 };
int ret1 = 0;
int ret2 = 0;
research(arr, sizeof(arr)/sizeof(arr[0]), &ret1,&ret2);
printf("这两个数字是:%d %d\n", ret1, ret2);
system("pause");
}
总结:任意选择一个二进制位,然后将数组分成两部分,其中一部分的末位是1,另一部分是0。由于这两个单独的数末位肯定不一样,所以分组后肯定不会在同一个组内。这个问题的解决思路就和上面比较相似了。
从上面的几个例子可以看出,查找缺失的数字主要是通过位运算符来抵消满足条件的元素,而将那些缺失的元素进行保存,最后在将缺失的元素输出到屏幕上。