位运算(leetcode官方题解,笔记)

106 阅读2分钟

338. 计算二进制表示中1的个数

Brian Kernighan 算法

public int countOnes(int x) { 
    int ones = 0;
    while (x > 0) { 
        x &= (x - 1); 
        ones++;
    } 
    return ones; 
}

x &= (x - 1)每执行一次,x 的二进制表示减少且仅减少一个 1
原理:

xx - 1x & (x - 1)
100
100100
100011000
100001110000
. . .

以此类推。对二进制数假设一个切割,如11011看作11011的组合。执行一次x &= (x - 1)1变成了011011变成了11010,减少了一个1。现在,把11010看作11010的组合,得到11000,即11000,再次减少一个1。这个切割为从右往左第一个1开始。
总结:x = x & (x - 1)x 的二进制表示中的最低位数的 1 变为0之后的数。

动态规划——最高有效位

public int[] countBits(int n) {
    int[] bits = new int[n + 1];
    int highBit = 0;
    for (int i = 1; i <= n; i++) {
        if ((i & (i - 1)) == 0) {
            highBit = i;
        }
    bits[i] = bits[i - highBit] + 1;
    }
    return bits;
}

二进制数的最高位为1,将最高位转换为0,同样能将二进制数减少一个1,如1100->0100。循环是单调递增的,当i & (i - 1) == 0时,此时的i2的整数次幂(形如10000),记录为highBit,为后续的i的同等位数的2的整数次幂。i1的个数等于i - highBit1的个数加1。如bits[1110] = bits[110] + 1(其中的数为二进制表示)。

动态规划——最低有效位

public int[] countBits(int n) {
    int[] bits = new int[n + 1];
    for (int i = 1; i <= n; i++) {
        bits[i] = bits[i >> 1] + (i & 1);
       }
    return bits;
}

动态规划——最低设置位

public int[] countBits(int n) {
    int[] bits = new int[n + 1];
    for (int i = 1; i <= n; i++) {
        bits[i] = bits[i & (i - 1)] + 1;
    }
    return bits;
}

参考上述Brian Kernighan 算法,ii & (i - 1)多一个1

要点总结

  1. 涉及数组,优先考虑动态规划
  2. i & (i - 1) == 0的判断, x = x & (x - 1)的更新
  3. 按位与&的应用

136. 只出现一次的数字

问题: 对于整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

public int singleNumber(int[] nums) {
    int single = 0;
    for (int num : nums) {
        single ^= num;
    }
    return single;
}

异或^运算有三个性质:

  1. 任何数和0做异或运算,结果仍然是原来的数
  2. 任何数和其自身做异或运算,结果是0
  3. 异或运算满足交换律和结合律

数组中的全部元素的异或运算结果即为数组中只出现一次的数字。