【C++】位操作符经典oj题

1.只出现一次的数字(1)

地址我就不发出来了力扣,牛客都可以资源。

 各位还记得按位操作符吗?这个题用按位异或嘎嘎板正嘎嘎牛。

  • 按位与(&)有0就是0,全1才是1。
  • 按位或(|)有1就是1,全0才是0。
  • 按位异或(^)相同为0,相异为1。

看个例子:

 也就是说只要是相同的两个数按位异或就是0,如果我把所有的数按位异或,相同的全部消去了,最后按位异或和(简称异或和)就是单身狗的值。

 中间那个for循环叫--范围for(名字挺高级,也就这)

  • auto:推演出范围变量的数据类型。根据推理,e的类型就是int,也就是auto相当于int
  • e:是范围变量的名称,该变量在循环迭代期间接受不同数组元素的值。e相当于nums了。

2.只出现一次的数字(2)

这个比上一个有点意思,有很多意思了。 

 

 这个题再用按位异或就会寸步难走。疑惑后重复的消掉了,但是不重复的都保留下来了。

我们能找到两个有力条件

  1. -2^31 <= nums[i] <= 2^31 - 1,这意味着我们处理的数据都是32位的数据。
  2. 其他元素出现三次。

既然是二进制了,那我们让每一位的是都相加,那么某一位如果是3的倍数说明单身狗在这一位为0,奇数位说明单身狗有值。

  • 让二进制每一位相加

nums[]={2,2,3,2}

2=00000000000000000000000000000010

2=00000000000000000000000000000010

3=00000000000000000000000000000011

2=00000000000000000000000000000010

sum=00000000000000000000000000000041

sum的值不在遵循二进制,它就相当于我们的一个计数器。

  • 再将sum进行模3,保留的就是单身狗的。

00000000000000000000000000000041%3=00000000000000000000000000000031

然后把二进制转换为十进制就是结果。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res = 0;
        for (int i = 0; i < 32; ++i) {
            int cnt = 0;
            for (auto x : nums) {
                cnt += (x>>i)&1;
            }
            res |= (cnt%3)<<i;
        }
        return res;
    }
};

(num>>i)&1的意思是右移第i位然后按位与1操作是只进行最后一位想加,让其他位为0(因为我们是一位一位来的)。

 res |= (cnt%3)<<i;是先取模,然后一位一位左移,再一位一位相加。刚开始res是0,按位或有1就是1。

3.只出现一次的数字(3)

这个也有点意思但是意思不多,甚至不如上面那个有意思,但是这个水平高。

 第一个是有一个单身狗,这个有一对单身狗,再按位异或它的异或和还是两个单身狗异或的结果,所以我们得出结论:这个题异或不行在这里我只想说不能光靠异或啊,他又不是美丽国队长。



nums=[1,2,1,3,2,5],异或后的结果是3和5的异或和。现在我咋样才能把他俩分开,就根据唯一的条件异或和把他俩分开。我们想一下第一题,只有一个单身狗,那我们也分两个小组,把这一对单身狗分开,但是我怎样分组,奇数偶数行吗?这个例子就是两个奇数。划界限?怎么划界限?也不行。

那我们想一下

 0000 0011  3 
^0000 0101  5
 0000 1110  
 

按位异或的条件是啥,相同为0,相异为1,也就是说,有1的那一位,两个单身狗的那一位必定不同。第1,2,3位就是。

我们根据位数是1的来进行分组,那这俩单身狗一定在不同组,其他成对的一定分一组。

在不同组我们进行异或即可。

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        unsigned int target = 0;
        // 一对相同的数字异或为0
        // 所以target为两个只出现了一次的元素的异或
        for (auto n: nums) {
            target ^= n;
        }

        // 因为两数不同,lowbit必然不为0
        // 物理意义就是两个不同的数字不同的最低的位在哪
        int lowbit = target & (-target);

        int a1 = 0;
        int a2 = 0;

        // 重复上述的过程,但是将nums按照lowbit为1或者为0分类
        // 则两个数必然被分到不同的类目;而相同的数字一定在同一个类目
        // 所以按类目分别异或就可以得到两个不同的数字
        for (auto n: nums) {
            if (n & lowbit) {
                a1 ^= n;
            } else {
                a2 ^= n;
            }
        }

        vector<int> ans;
        ans.push_back(a1);  //把,a1,a2分别插入ans中
        ans.push_back(a2);

        return ans;
    }
};


target=1110,-target=0010(0001+1).

用范围for遍历,我们选定的这一位来分组,这一位是1的在一组,0的在下一组,每一组在按位异或,就能得到两个单身狗了。

4.求最大连续bit数

 这个题也需要用到二进制转换这个题有点意思。



其实这个题思路很明确,就是找个计数器count记录连续数字1,max_num记录最大连续数。

这个题中也就是见到1就记录下来。但是如何将十进制转化为二进制的呢?——按位与1就可以了。

#include <iostream>
#include <vector>
using namespace std;

int main(){
    int n,max_num=0,count=0;
    cin>>n;
    while(n)
    {
        if(n&1) {    //找每一位是1的。模1还是1
            count++;
        }
            
        else {
            count=0;
        }
        n>>=1;
    }
    max_num=max(count,max_num);
    cout<<max_num;
}

 代码就写好了,但是有一点点小岔劈。运行不通过,

 

改进:

 

但是就这一点错误吗?

如果是负数呢?我们输入-1试一试。

 

 为啥是运行结果过大呢?

-1的二进制是   1111 1111(1111 1110+1) ,当我们右移-1时,左边出现空缺就会补1,一直右移,一直补1,那这辈子也干不完啊。所以我们来个循环。

#include <iostream>
#include <vector>
using namespace std;

int main(){
    int n,max_num=0,count=0;
    cin>>n;
    for(int i=0;i<32;i++)
    {
          if(n&1)
        {
        count++;
         max_num=max(count,max_num);
        }
        else
        {
            count=0;
        }
        n>>=1;
    }
    max_num=max(count,max_num);
    cout<<max_num;
    
}

这样就避免了负数的一直补1问题。

本文写的比较仓促,如有问题或错误希望各位大神指正。 

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值