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

在学习完位操作后,经常会遇到一类关于查找缺失整数的问题。
第一类是给你一个数组,告诉你这些数字的范围是什么,然后让你查找这个缺失的数字(例如无序数组的范围是从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
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值