【求二进制中1的个数】hamingWeight汉明重量

 思考这个问题的起因是看到了力扣191题的解法,最开始我是直接取每位二进制位累加1的个数。

起先看不太懂hamingWeight的做法是在干嘛,后来得知似乎是jdk源码的实现逻辑,看了一些文章,自己做了一些总结。

以下将以这段代码为例进行详细解释:

public int bitCount(int i) {
    i = i - ((i >>> 1) & 0x55555555); // 1
    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333); // 2
    i = (i + (i >>> 4)) & 0x0f0f0f0f; // 3
    i = i + (i >>> 8); // 4
    i = i + (i >>> 16); // 5
    return i & 0x3f;
}

一、每2位存储1的个数

只需将这2位数-高位上的值,具体如下:

二进制位1的个数(十进制)1的个数(二进制)
11 = 33 - 1 = 210
10 = 22 - 1 = 101
01 = 11 - 0 = 101
00 = 00 - 0 = 000
i = i - ((i >>> 1) & 0x55555555); // 1

(i >>> 1) & 0x55555555:这部分的作用是将2位中高位的值获取到低位,高位为0。

i = i - ((i >>> 1) & 0x55555555):每2位都减去高位上的值,也就是上面表格所说的-1,这样每2位就存储了2位中1的个数。

以1111 1111 1111 1111 1111 1111 1111 1101为例

11 11 11  11 11 11  11 11 11 11 11 11  11  11 11 01
10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 01
2    2   2   2   2   2   2   2   2   2   2   2   2   2   2   1
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333); // 2

这部分代码的作用是将每2位存储的1的个数存在4位中,也就是把2位+2位为一组。

2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   1
2位+2位为一组
     4        4        4         4        4       4         4        3
i = (i + (i >>> 4)) & 0x0f0f0f0f; // 3

同理,这部分代码的作用是将每4位存储的1的个数存在8位中,也就是把4位+4位为一组。

     4        4        4         4        4       4         4        3
4位+4位为一组
               8                   8                  8                  7(8位)
i = i + (i >>> 8); // 4

这部分的代码是将相邻的8位中1的个数聚集在一起

             8                   8                  8                  7(8位)
相邻的8位中1的个数聚集在一起
             8                   16                16                 15
i = i + (i >>> 16); // 5

这部分的代码是将相邻的16位中1的个数聚集在一起

             8                   16                16                 15
将相邻的16位中1的个数聚集在一起
             8                   16                24                 31

最后的低6位也就是二进制位中1的个数。

这个地方可能有一个疑问,就是为什么>>>8和>>>16的时候不需要做与操作呢?

不妨设>>>8之前每8位,分别为D4、D3、D2、D1,Di表示第i个8位中1的个数。

D4           D3           D2            D1      
>>>8                D4           D3           D2            D1   
+                D(4+3)     D(3+2)    D(2+1)     D1 
                 D(4+3)            D(3+2)           D(2+1) 
>>>16                                                              D(4+3)   ....
+                                                              D(4+3+2+1)

从上表可以看出,经过4、5两步后,最后的低8位存放的是32位中1的个数。

return i & 0x3f;

最后,返回的结果是取低6位,从源头想,32位中1的个数最多是32位,只需要6位就可以表示。

当然我们也可以对4、5两步进行一些变化,由于4之前,每8位都存储了8位中1的个数,那么我们也可以直接将每个8位中1的个数相加,即:

i = i + (i >>> 8) + (i >>> 16) + (i >>> 24);

最终返回的时候仍然取低6位也是可以的。

二、每3位存储1的个数

结论:3位中1的个数 = 3位的值 - 高2位的值 - 高1位的值

二进制位高2位二进制位高1位2进制位3位中1的个数
000 = 000 = 000 - 0 - 0 = 0
001 = 100 = 001 - 0 - 0 = 1
010 = 201 = 102 - 1 - 0 = 1
011 = 301 = 103 - 1 - 0 = 2
100 = 410 = 214 - 2 - 1 = 1
101 = 510 = 215 - 2 - 1 = 2
110 = 611 = 316 - 3 - 1 = 2
111 = 711 = 317 - 3 - 1 = 3

三、每4位存储1的个数

结论:4位中1的个数 = 4位的值 - 高3位的值 - 高2位的值 - 高1位的值

public int bitCount(int n) {
    // 1
    n = n - ((n >>> 1) & 0x77777777) - ((n >>> 2) & 0x33333333) - ((n >>> 3) & 0x11111111);
    // 2
    n = (((n & 0xf0f0f0f0) >>> 4) + (n & 0x0f0f0f0f));
    // 3
    n = n + (n >>> 8);
    // 4
    n = n + (n >>> 16);
    return n & 63;
}

这段代码的第一步就是实现4位存储1的个数,后续步骤同每2位存储1的个数。

四、每多位存储1的个数

结论:n位中1的个数 = n位的值 - 高(n - 1)位的值 - 高(n - 2)位的值 - ... - 高1位的值

public int bitCount(int n) {
    n = n - ((n >>> 1) & 0xdef7bdef) - ((n >>> 2) & 0xce739ce7)
            - ((n >>> 3) & 0xc6318c63) - ((n >>> 4) & 0x02108421);
    n = ((n + (n >>> 5)) & 0xc1f07c1f);
    n = ((n + (n >>> 10) + (n >>> 20) + (n >>> 30)) & 63);
    return n;
}

以上这段代码是实现每5位存储1的个数,其中(n >>> 1) & 0xdef7bdef即为每5为中高4位的值,以此类推...

最后将1存储在每个10位中,把4个10位的1添加在一起,即为答案。

此处,也可以采用同每2位存储1的个数同样的思想,把相邻10位的1聚集起来,最后把相邻20位的1聚集起来,最后也可以得到32位中的1。

n = n + (n >>> 10);
n = n + (n >>> 20);

下附力扣题目链接,可自行练习:https://leetcode.cn/problems/number-of-1-bits/description/

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值