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。
原理:
| x | x - 1 | x & (x - 1) |
|---|---|---|
| 1 | 0 | 0 |
| 10 | 01 | 00 |
| 100 | 011 | 000 |
| 1000 | 0111 | 0000 |
| . . . |
以此类推。对二进制数假设一个切割,如11011看作1101和1的组合。执行一次x &= (x - 1),1变成了0,11011变成了11010,减少了一个1。现在,把11010看作110和10的组合,得到110和00,即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时,此时的i为2的整数次幂(形如10000),记录为highBit,为后续的i的同等位数的2的整数次幂。i的1的个数等于i - highBit的1的个数加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 算法,i比i & (i - 1)多一个1。
要点总结
- 涉及数组,优先考虑动态规划
i & (i - 1) == 0的判断,x = x & (x - 1)的更新- 按位与
&的应用
136. 只出现一次的数字
问题: 对于整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
public int singleNumber(int[] nums) {
int single = 0;
for (int num : nums) {
single ^= num;
}
return single;
}
异或^运算有三个性质:
- 任何数和
0做异或运算,结果仍然是原来的数 - 任何数和其自身做异或运算,结果是
0 - 异或运算满足交换律和结合律
数组中的全部元素的异或运算结果即为数组中只出现一次的数字。