Integer.bitCount(int i)方法

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

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

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

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

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

比如,先来一个简单的例子,数字7:。二进制里有几个1?很容易嘛,一眼就看出来有3个。

来一个复杂点的:,这里面有几个1?恐怕就不能一眼看出来了。

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

计算机不是这么算的,还是拿数字7这个举例:。

对二进制0111,一次看两位,第一次看到01,它里面有1个1,我们说它有个1,第二次看到11,它里面有2个1,我们说它有个1,组合起来,我们说它有个1。

再来分析01和11这么变成01和10的,一次看两位,,。

这么做的目的是:把原来二进制中1的个数用二进制值表示出来:里有1个1,用表示,里有2个1,用表示。

先上一段代码,即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;
}
十六进制 二进制
0x55555555 01010101010101010101010101010101
0x33333333 00110011001100110011001100110011
0x0f0f0f0f 00001111000011110000111100001111
0x3f 00000000000000000000000000111111

刚才,我们在做&运算的时候,用的是,如果把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;
}

拿来分析,,于是就出现了优化后的第一行的写法。

第二行保持不变。

第三行,做了一个&的结合律,等价于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位统计)

 

你可能感兴趣的:(随手记录)