191. 位1的个数
我的思路
-
每次取
n
的最低位,如果是1
,则结果加 1 ;然后n
右移 1 位,直到n
等于 0 . -
取出数字 a 的最低位:
a & 1
。
根据上述思路,很容易写出AC代码:
class Solution {
public:
int hammingWeight(uint32_t n) {
int ret = 0;
while (n){
if (n & 1){
ret ++;
}
n = (n >> 1);
}
return ret;
}
};
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:5.9 MB, 在所有 C++ 提交中击败了39.33%的用户
- 复杂度分析:
- 时间复杂度:
O(k)
,k的最大值是n的二进制位数。 - 空间复杂度:
O(1)
,只用到了可数个变量。
- 时间复杂度:
LeetCode上的方法二
-
这里主要用到了 位运算 的一个定理:
n & (n-1)
的结果,是将数字n
的二进制位中 最低位的1 变成 0 之后的结果。- 这个结论以前学过,可是到现在忘记了。果然,人类最大的敌人是遗忘。
-
也就是说,每次进行一次
n = n & (n-1)
,n中最低位的1将会变成0,直到n变成0为止。
根据上述思路,很容易写出AC代码:
class Solution {
public:
int hammingWeight(uint32_t n) {
int ret = 0;
while (n){
ret ++;
n &= (n - 1);
}
return ret;
}
};
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:5.9 MB, 在所有 C++ 提交中击败了65.43%的用户
- 复杂度分析:
- 时间复杂度:
O(logn)
。循环次数等于n的二进制中1的个数,最坏情况下,n的二进制位数全为1,此时需要循环logn次。
- 时间复杂度:
方法三:Integer.bitCount
- 这个方法是Java中的一个方法,源代码如下:
public static int bitCount(int i) {
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}
>>>
是逻辑右移,高位只会补0。>>
是算数左移,根据最高位是0还是1选择补0还是1。
对源码的解释参考了 大佬博客:Integer.bitCount函数解释 。
-
设:
i = b 0 ⋅ 2 0 + b 1 ⋅ 2 1 + … + b 30 ⋅ 2 30 + b 31 ⋅ 2 31 i = b_0\cdot2^0 + b_1\cdot2^1 + \ldots + b_{30}\cdot2^{30} + b_{31}\cdot2^{31} i=b0⋅20+b1⋅21+…+b30⋅230+b31⋅231
结果是求:$ \sum_{i=0}^{31}b_i$ . -
第一步:
i = i - ((i >>> 1) & 0x55555555);
i >>> 1
= b 1 ⋅ 2 0 + b 2 ⋅ 2 1 + … + b 30 ⋅ 2 29 + b 31 ⋅ 2 30 b_1\cdot2^0 + b_2\cdot2^1 + \ldots + b_{30}\cdot2^{29} + b_{31}\cdot2^{30} b1⋅20+b2⋅21+…+b30⋅229+b31⋅2300x55555555
=0101 0101 0101 0101 0101 0101 0101 0101b
- 注意右边是低位。
((i >>> 1) & 0x55555555)
= b 1 ⋅ 2 0 + b 3 ⋅ 2 2 + … + b 29 ⋅ 2 28 + b 31 ⋅ 2 30 b_1\cdot2^0 + b_3\cdot2^2 + \ldots + b_{29}\cdot2^{28} + b_{31}\cdot2^{30} b1⋅20+b3⋅22+…+b29⋅228+b31⋅230- 相当于只取偶数位(最低位是第0位)。
i - ((i >>> 1) & 0x55555555)
= b 0 ⋅ 2 0 + b 1 ⋅ ( 2 1 − 2 0 ) + b 2 ⋅ 2 2 + b 3 ⋅ ( 2 3 − 2 2 ) + b 4 ⋅ 2 4 … + b 31 ⋅ ( 2 31 − 2 30 ) = ( b 0 + b 1 ) ⋅ 2 0 + ( b 2 + b 3 ) ⋅ 2 2 + … + ( b 28 + b 29 ) ⋅ 2 28 + ( b 30 + b 31 ) ⋅ 2 30 b_0\cdot2^0 + b_1\cdot(2^1-2^0)+b_2\cdot2^2+b_3\cdot(2^3-2^2)+b_4\cdot2^4\ldots+b_{31}\cdot(2^{31}-2^{30}) \\ = (b_0+b_1)\cdot2^0+(b_2+b_3)\cdot2^2+\ldots+(b_{28}+b_{29})\cdot2^{28}+(b_{30}+b_{31})\cdot2^{30} b0⋅20+b1⋅(21−20)+b2⋅22+b3⋅(23−22)+b4⋅24…+b31⋅(231−230)=(b0+b1)⋅20+(b2+b3)⋅22+…+(b28+b29)⋅228+(b30+b31)⋅230- 因此
i
= ( b 0 + b 1 ) ⋅ 2 0 + ( b 2 + b 3 ) ⋅ 2 2 + … + ( b 28 + b 29 ) ⋅ 2 28 + ( b 30 + b 31 ) ⋅ 2 30 (b_0+b_1)\cdot2^0+(b_2+b_3)\cdot2^2+\ldots+(b_{28}+b_{29})\cdot2^{28}+(b_{30}+b_{31})\cdot2^{30} (b0+b1)⋅20+(b2+b3)⋅22+…+(b28+b29)⋅228+(b30+b31)⋅230
-
由于敲公式不方便,这里便不再展示第二至第五步的过程了,可以根据第一步,自行进行推导。
-
经过第五步:
i = i + (i >>> 16);
得到:
i = ( b 0 + b 1 + b 2 + … + b 30 + b 31 ) ⋅ 2 0 + ( b 8 + b 9 + … + b 30 + b 31 ) ⋅ 2 8 + ( b 16 + b 17 + … + b 30 + b 31 ) ⋅ 2 16 + ( b 24 + b 25 + … + b 30 + b 31 ) ⋅ 2 24 i = (b_0+b_1+b_2+\ldots+b_{30}+b_{31})\cdot2^0+(b_8+b_9+\ldots+b_{30}+b_{31})\cdot2^8\\+(b_{16}+b_{17}+\ldots+b_{30}+b_{31})\cdot2^{16}+(b_{24}+b_{25}+\ldots+b_{30}+b_{31})\cdot2^{24} i=(b0+b1+b2+…+b30+b31)⋅20+(b8+b9+…+b30+b31)⋅28+(b16+b17+…+b30+b31)⋅216+(b24+b25+…+b30+b31)⋅224 -
第六步:
return i & 0x3f;
显然, 2 0 2^0 20前的系数已经是我们需要的结果了。显然, ∑ i = 0 31 b i ≤ 32 \sum_{i=0}^{31}b_i \le 32 ∑i=031bi≤32,因此只需要将i
和0x3f
=0011 1111
相与即可得到结果。
根据上述思路,很容易写出AC代码:
class Solution {
public:
int hammingWeight(uint32_t n) {
n = (n & 0x55555555) + ((n >> 1) & 0x55555555);
n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f);
n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff);
n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff);
return n;
}
};
执行用时:4 ms, 在所有 C++ 提交中击败了38.33%的用户
内存消耗:5.8 MB, 在所有 C++ 提交中击败了72.01%的用户
-
注意:位运算的优先级较低,因此最好将所有的位运算都加上括号。
-
上述代码和
Integer.bitCount
中的代码是一致的,只不过更容易记忆且理解。 -
复杂度分析:
- 时间复杂度:
O(1)
,执行了5行代码。 - 空间复杂度:
O(1)
,没有使用任何额外变量。
- 时间复杂度:
后记
- 对了,一个数字的二进制中1的个数叫做 汉明重量 。
- 突然发现,方法三,这就是本科阶段 计算机系统原理 做过的csapp的实验啊:
/*
* parityCheck - returns 1 if x contains an odd number of 1's
* Examples: parityCheck(5) = 0, parityCheck(7) = 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 20
* Rating: 4
*/
int parityCheck(int x) {
x = (x &0x55555555) + ((x >>1) &0x55555555) ;
x = (x &0x33333333) + ((x >>2) &0x33333333) ;
x = (x &0x0f0f0f0f) + ((x >>4) &0x0f0f0f0f) ;
x = (x &0x00ff00ff) + ((x >>8) &0x00ff00ff) ;
x = (x &0x0000ffff) + ((x >>16) &0x0000ffff) ;
return (x & 1) ;
}
- 打开我的移动硬盘,找到了当年写的代码(如上)。
- 只怪自己17年秋天没有好好学习啊。
2021.3.22 23:41