• 刷题笔记2:用位运算找“只出现一次的一个数”


    1. & 和 | 的基本操作

    137. 只出现一次的数字 II - 力扣(LeetCode)

    先对位运算的操作进行复习:

    1、>> 右移操作符

    移位规则:⾸先右移运算分两种:
    1. 逻辑右移:左边⽤0填充,右边丢弃
    2. 算术右移:左边⽤原该值的符号位填充,右边丢弃
    一般的编译器都默认使用算术右移。

    2. << 左移操作符

    移位规则:左边抛弃、右边补0  

     注意,所有的操作符只能移动整数。

    3. & 和 |

    1. & //按位与
    2. | //按位或
    &:两位数都为1时为1,其余时候为0
    |  :  两位数只要有一个为1,就为1,其余时候为0
    并且,| ^ &的优先级都是低于==和!=的, 需要判断语句时记得加括号
    意义

    & :

    • 清零特定位 (m中特定位置0,其它位为1,s=s&m或s&=m)
    • 取某数中指定位 (m中特定位置1,其它位为0,s=s&m或s&=m)  比如 最经典的
      1. for(i=0;i<32;++i) //int是32位二进制整数
      2. int ans =(x>>i)& 1;

             由于1的补码是 000...0001,x每次右移1位来将每一位分别和0000....0001进行按位与,因此每一轮循环都能将x的一位赋值给ans,让ans依次得到x的每一个二进制位。

    | :

    多用于将左操作数某些位置1,其它位不变 (m特定位置为1,其余位置为0,希望将s的特定位置改为1,s |= m)

    经典:根据特定的条件将ans中的某几位调整为1(将0000..0001中的1一位一位的往前挪)

    1. if(.....)
    2. ans |= (1 << i);


    有了以上铺垫,我们再学习思路:

      由于除了答案,其余每个数字出现三次,所以这三个相同数字为一组中,就有三个一模一样的32为二进制数据(3个1或3个0),我们将第n位上的所有数据加在一起再对3取模,得到的数据就是ans在这一个二进制位上该有的数据。(很多个3和0取出来的模都为0,此时不论ans对应的这一位是1或0,都能被正确赋值) 

    答案:

    1. int singleNumber(vector<int>& nums) {
    2. int ans = 0;
    3. for (int i = 0; i < 32; ++i) {
    4. int x = 0;
    5. for (int j = 0; j < nums.size(); ++j) {
    6. x += (nums[j] >> i) & 1;
    7. }
    8. if (x % 3) {
    9. ans |= (1 << i);
    10. }
    11. }
    12. return ans;
    13. }

     2.小试牛刀

    136. 只出现一次的数字 - 力扣(LeetCode)

     

    依然是找出只出现一次的数据,不过按位异或就可以了

    ^  : 双目运算符按位异或,相异位1,相同为0

    1. int singleNumber(vector<int>& nums) {
    2. int ans=0;
    3. for(auto e:nums){
    4. ans^=e;
    5. }
    6. return ans;
    7. }

    3.位运算 x & -x 来获取x的lowbit(取出二进制下X最低位的1)

            假设x为正数(位操作符都只对整数有用),在x变成-x时,除了第一位的符号位变化外,在由原码变到反码再到补码(取反加一)时,取反加一中的 “加一”,一定会加到反码由低到高的第一个0上,而反码所有的0都对应的是原码(正数)的1,(所以反码的第一个0对应的就是原码的第一个1)当反码的第一个0(假定这个0在p位置)被加成1后,就可以通过&运算或得  “最低位在p位置并且是1”的数据。

           我们可以认为x &-x是一种取得最低位1的技巧,并且这个用法对负数也凑效。

    并且,在y = x & -x后,y是个除了符号位和p位置以外,其余位置都是0的数。

    来个大的:

    260. 只出现一次的数字 III - 力扣(LeetCode)

                        

         就像刚刚的小试牛刀,我们可以用异或消掉除了ans数组(用于存放两个只出现一次的数a, b)之外的所有数据。不过这两个被异或在一起的数据(x = a^b)该怎么分开呢?

    分开的方法:

        对于x,x的每一个1,一定都是由一个数的1和另一个数的0组成的。

    在这个理论的基础上,我们通过x & -x来获得一个和x的最低位1(假设在位置p)相同,其他(除最高位)都是0的数字y。我们通过 判断位置p是否为1将nums数组分成两组,所以ans数组中的两个数字一定就被分开了,两组中剩下的其他数字一定是成对出现的,将两个组分别全部异或就能得到答案。

    没有全部通过,原因是当测试用例中有x为-2^31时,-x(2^31)没法被放进int

     我们处理如下:

    会在x处出现-2^31次方的,答案一定是0和-2^31,否则x不会等于-2^31

    并且在下文中,0和-2^31也的确会分开,因此达到目的。

    各位都是工科生,仔细想想按位异或的最后一步就明白了。

  • 相关阅读:
    智能家居监控管理系统项目需求分析
    sqlmap中文文档
    “下一个江小白”靠什么成就?
    “行业寒冬”,字节10年测试工程师给在座的测试人一些涨薪建议
    python 断点续传下载
    GBDT(梯度提升树 Gradient Boosting Decison Tree)学习笔记
    Mybatis学习笔记9 动态SQL
    V4L2框架
    26 分钟惊讶世界,GPT-4o 引领未来人机交互
    手握“发展密钥”,TCL科技或迎价值重估?
  • 原文地址:https://blog.csdn.net/2301_79501467/article/details/139628590