Integer.bitCount(int i)方法

昨天,在LeetCode刷题的时候,在Submission中看到一个执行用时很少的代码,里面用到了Integer.bitCount(i)方法,没明白是做什么的,看了注释后,才知道这个bitCount(i)方法是统计整形数字i对应的二进制中,有几个1,但是写法却让人迷惑,下面就来分析一下。

题目链接:1558. 得到目标数组的最少函数调用次数

先搜索了几篇文章看了下,大概意思渐渐理解了,做下记录。

参考博客:https://segmentfault.com/a/1190000015763941https://blog.csdn.net/u011497638/article/details/77947324https://blog.csdn.net/zhouzipeng000/article/details/56676885

给你一个数i,让你求i对应的二进制表示中,有几个1。

比如,先来一个简单的例子,数字7:7_{(10)}=0111_{(2)}。二进制里有几个1?很容易嘛,一眼就看出来有3个。

来一个复杂点的:144358622_{(10)}=1000100110101011110011011110_{(2)},这里面有几个1?恐怕就不能一眼看出来了。

这里所说的一眼看出来,就是我们用人眼从左到右扫描了一遍,对每一位做一次校验,判断这一位是1,累加一次,最终得到结果。

计算机不是这么算的,还是拿数字7这个举例:7_{(10)}=0111_{(2)}

对二进制0111,一次看两位,第一次看到01,它里面有1个1,我们说它有01_{(2)}个1,第二次看到11,它里面有2个1,我们说它有10_{(2)}个1,组合起来,我们说它有01_{(2)}+10_{(2)}=11_{(2)}=3_{(10)}个1。

再来分析01和11这么变成01和10的,一次看两位,01_{(2)}=(01_{(2)} \& 01_{(2)}) + ((01_{(2)} >>> 1) \& 01_{(2)})10_{(2)}=(11_{(2)} \& 01_{(2)}) + ((11_{(2)} >>> 1) \& 01_{(2)})

这么做的目的是:把原来二进制中1的个数用二进制值表示出来:01_{(2)}里有1个1,用01_{(2)}表示,11_{(2)}里有2个1,用10_{(2)}表示。

先上一段代码,即bitCount()的原型:

public static int bitCount(int i) {
    i = (i & 0x55555555) + ((i >>> 1) & 0x55555555);
    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
    i = (i & 0x0f0f0f0f) + ((i >>> 4) & 0x0f0f0f0f);
    i = (i & 0x00ff00ff) + ((i >>> 8) & 0x00ff00ff);
    i = (i & 0x0000ffff) + ((i >>> 16) & 0x0000ffff);
    return i;
}
十六进制二进制
0x5555555501010101010101010101010101010101
0x3333333300110011001100110011001100110011
0x0f0f0f0f00001111000011110000111100001111
0x3f00000000000000000000000000111111

刚才,我们在做&运算的时候,用的是01_{(2)},如果把16个01_{(2)}拼接起来,就是55555555_{(16)}了,对于代码的第一行,是将二进制每次看两位,统计每两位中1的个数,再放到原位置上。

如果第一行能懂,再看第二行,同理,就是每次看二进制的四位,统计四位中1的个数,再放到原位置上。

……

第五行,每次看二进制的32位,统计32位中1的个数,再放到原位置上。

此时,bitCount()的原型就能理解了。再看bitCount()的优化部分。

public static int bitCount(int i) {
    // HD, Figure 5-2
    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;
}

11_{(2)}\rightarrow 10_{(2)}来分析,10_{(2)}=11_{(2)}-((11_{(2)}>>>1) \& 01_{(2)}),于是就出现了优化后的第一行的写法。

第二行保持不变。

第三行,做了一个&的结合律,等价于i = (i & 0x0f0f0f0f) + ((i >>> 4) & 0x0f0f0f0f);。

第四行和第五行,因为一个int,假设它的二进制全是1,最多也就是32个1,在运算中出现了进位先忽略,最后&一个0x3f,把前面的进位给去掉即可。

public static void main(String[] args) {
    int i = 144358622;
    System.out.println("i的十进制表示:                         " + Integer.toBinaryString(i));
    i = i - ((i >>> 1) & 0x55555555);
    System.out.println("每次看 2位,统计 2位里1的数量,放到原位置上:" + Integer.toBinaryString(i));
    i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
    System.out.println("每次看 4位,统计 4位里1的数量,放到原位置上:" + Integer.toBinaryString(i));
    i = (i + (i >>> 4)) & 0x0f0f0f0f;
    System.out.println("每次看 8位,统计 8位里1的数量,放到原位置上:" + Integer.toBinaryString(i));
    i = i + (i >>> 8);
    System.out.println("每次看16位,统计16位里1的数量,放到原位置上:" + Integer.toBinaryString(i));
    i = i + (i >>> 16);
    System.out.println("每次看32位,统计32位里1的数量,放到原位置上:" + Integer.toBinaryString(i));
    i = i & 0x3f;
    System.out.println("最终结果的二进制表示:" + Integer.toBinaryString(i));
}

注意,Integer.toBinaryString()会忽略前导0,分析的时候,需要手动添加,下面是我手动模拟一遍的数据。

00-00-10-00-10-01-10-10-10-11-11-00-11-01-11-10(原始数据)
00-00-01-00-01-01-01-01-01-10-10-00-10-01-10-01(每2位统计)
0000--0001--0010--0010--0011--0010--0011--0011 (每4位统计)
00000001----00000100----00000101----00000110   (每8位统计)
0000000100000101--------0000100100001011       (每16位统计)
00000001000001010000101000010000               (每32位统计)

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值