191. 位1的个数

这篇博客介绍了如何利用位操作高效地计算一个整数中二进制位1的个数。讨论了三种方法:直接迭代、位运算定理和Integer.bitCount方法。文章详细解析了每种方法的实现思路和时间复杂度,并提供了C++代码示例。最后,作者回顾了这些技巧在本科计算机系统原理课程中的应用。
摘要由CSDN通过智能技术生成

191. 位1的个数

LeetCode: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函数解释

  1. 设:
    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=b020+b121++b30230+b31231
    结果是求:$ \sum_{i=0}^{31}b_i$ .

  2. 第一步: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} b120+b221++b30229+b31230
    • 0x55555555 = 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} b120+b322++b29228+b31230
      • 相当于只取偶数位(最低位是第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} b020+b1(2120)+b222+b3(2322)+b424+b31(231230)=(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
  3. 由于敲公式不方便,这里便不再展示第二至第五步的过程了,可以根据第一步,自行进行推导。

  4. 经过第五步: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

  5. 第六步:return i & 0x3f; 显然, 2 0 2^0 20前的系数已经是我们需要的结果了。显然, ∑ i = 0 31 b i ≤ 32 \sum_{i=0}^{31}b_i \le 32 i=031bi32,因此只需要将i0x3f=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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值