位运算解决“一个数组中,只有一个数字出现n次,其他数字出现k次”问题

来源:互联网 发布:淘宝卖家创业故事 编辑:程序博客网 时间:2024/05/22 16:56

在学习完位操作后,经常会遇到一类关于查找缺失整数的问题。
第一类是给你一个数组,告诉你这些数字的范围是什么,然后让你查找这个缺失的数字(例如无序数组的范围是从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。由于这两个单独的数末位肯定不一样,所以分组后肯定不会在同一个组内。这个问题的解决思路就和上面比较相似了。

从上面的几个例子可以看出,查找缺失的数字主要是通过位运算符来抵消满足条件的元素,而将那些缺失的元素进行保存,最后在将缺失的元素输出到屏幕上。

0 0
原创粉丝点击