位运算在一类数组题中的用法 只出现一次的数字I

前言

LeetCode上有几道题特别相似,分别是leetcode 136只出现一次的数(简单)137只出现一次的数Ⅱ(中等)260只出现一次的数Ⅲ
这几道题当然有很多中解法,但是其中有一种解法特别独特,也是很多人无法想到的,那么就是利用位运算,说起位运算,其实很多人都忘记了,只是知道怎么回事,但是不知道它具体有什么用处。读完本文,你就能感觉到它的神奇之处了

复习

既然需要使用位运算,那么在这里我们就先复习一下吧

在这里插入图片描述
左移和右移
在这里插入图片描述

一:只出现一次的数字I

题目
在这里插入图片描述
这道题需要借助到异或运算符对于参与运算的两个对象,如果对应二进制位不同结果是1,否则就是0。主要使用的性质有**:0异或任何数等于任何数,任何数异或自己都等于0,异或运算满足结合律和交换律**

所以对于上面的数组【4,1,2,1,2】,我们可以4 ^ 1 ^ 2 ^ 1 ^ 2 ,也就是4 ^ 1 ^ 1 ^ 2 ^ 2,所以4 ^ (1 ^ 1) ^ (2 ^ 2),所以4 ^ 0 ^ 0,所以结果就是4

class Solution {
public:
    int singleNumber(vector<int>& nums) 
    {
        int value=0;
        for(auto e : nums)
        {
            value=value^e;
        }

        return value;
    }
};

二:只出现一次的数字II

题目
在这里插入图片描述
对于这道题,有一种方法是自动机,但是比较难以理解,详情可以看这篇文章的讲解
但我们最常使用的还是直接用位运算

首先需要发现规律,将我们的数的二进制位每一位对应相加,然后对每一位的和进行取余
在这里插入图片描述
这里大家注意观察最终取余的结果和目标值。之所以这样做的原因是因为,如果其他书都出现了3次,只有目标数出现了1次,那么每一位为1的个数无非就是3的倍数或3的倍数+1,而3的倍数+1也就是我们要找的那个情况

代码如下:

class Solution
{
public:
    int singleNumber(vector<int>& nums)
    {
        int ret=0;
        for(int i=0;i<32;i++)
        {
            int count=0;
            for(int j=0;j<nums.size();j++)
            {
                if((nums[j]>>i & 1) == 1)
                {
                    count++;
                }
                
            }
            if(count%3!=0)
            {
                ret=ret | 1 << i;
            }


        }

    return ret;
    }


};

第一个for循环用于控制数字的每一个二进制位,进入第一个for循环之后,定义一个count,用来保存此时各数字对应二进制位的和,然后第二个循环首先判断该数字的这一二进制位是否是1,判断时只需要让这个数字右移由i控制的位数,让判断的位置跑到最低位,然后与1进行与运算,因为与运算是两者“同时是1才能是1”,所以一旦结果不等于1,说明判断的这一位就不是1,如果是1则count++,第二个for循环结束后,所有数字对应的二进制位加和就完毕了,接着根据之前的性质判断,如果取余不等于0,说明count是3的倍数+1,是由于那个单独出现的数字导致的,因此ret与1进行或运算,让其最后一位变为1(这个1就是单独出现的数字的某一二进制位),由于之前右移了i位,所以现在让其左移i位,回正
在这里插入图片描述
在这里插入图片描述

二:只出现一次的数字III

在这里插入图片描述
这道题和第一题有点相似,但是它是有两个单独出现的数字,如果直接使用异或,那么得到结果就是拿两个目标数的异或值,肯定是不行的

所以我们可以这样做,将元素分为两组,每组包含一个目标值,然后组内异或,那么每组的最终结果就是一个目标值

比如【a,b,a,b,c,d,e,f,e,f】进行分组后,组A为:【a,a,b,b,c】,组B为:【e,e,f,f,d】,组A得到c,组B得到d。
c和d不同,所以其异或的结果一定不是0,我们只研究其最右面那一位(其实任何一位都可以),也就是让其异或结果变为0或1,接着以0或1作为标准,让数组内的元素进行分组,比如说异或结果是1,我就这样判断:和1与运算的结果为1的分为1组,为0的分为另外一组我不需要管它到底在哪一组,我只明白相同的数字肯定在一组

那么最后一点就是如何让其异或结果变为0或1呢,假设异或结果是
temp,只需要temp & (-temp),因为负数等原数按位取反+1
在这里插入图片描述

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) 
    {
        int temp=0;
        for(auto e : nums)
            temp ^=e;//保留两个不同的数的异或值

        unsigned int div=temp &  (-(unsigned int)temp);//注意这是32位环境,注意强转为无符号

        vector<int> group(2.0);
        for(auto e : nums)
        {
            if((e & div)==0)//组内异或
            {
                group[0] ^= e;
            }
            else
            {
                group[1] ^=e;
            }

        }
        return group;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐江湖

创作不易,感谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值