385,位1的个数系列(二)

想了解更多数据结构以及算法题,可以关注微信公众号“数据结构和算法”,每天一题为你精彩解答。也可以扫描下面的二维码关注
在这里插入图片描述

前面我们讲了364,位1的个数系列(一),使用了代码和图形结合的方式,很容易理解,下面再来看另一种解法,如果之前没看过的话,估计不太容易想到。int类型是32位,每位要么是0要么是1,其实我们完全可以把1的个数存储在这二进制位中,我们先来看一个每两位两位存储的方式


1,每两位存储

我们先来看一下,两位二进制位总共有4种表示,分别是00,01,10,11。他们中1的个数分别是0,1,1,2,最大值是2,而两位二进制表示的最大数是3,所以足够存储了,我们就以-3为例来画个图分析一下
在这里插入图片描述
在这里插入图片描述

看明白了上面的图,代码就很好写了,我们来看下

public int bitCount(int n) {
    n = ((n & 0xaaaaaaaa) >>> 1) + (n & 0x55555555);
    n = ((n & 0xcccccccc) >>> 2) + (n & 0x33333333);
    n = (((n & 0xf0f0f0f0) >>> 4) + (n & 0x0f0f0f0f));
    n = n + (n >>> 8);
    n = n + (n >>> 16);
    return n & 63;
}

这里以0x开头的数字是16进制的,乍一看,上面密密麻麻的数字看起来可能容易犯晕,其实不用怕,我们只需要把上面的16进制改为二进制就很容易看明白了,我们来打印一下

1    System.out.println("16进制0xaaaaaaaa转化为二进制是----->" + Util.bitInt32(0xaaaaaaaa));
2    System.out.println("16进制0x55555555转化为二进制是----->" + Util.bitInt32(0x55555555));
3    System.out.println("16进制0xcccccccc转化为二进制是----->" + Util.bitInt32(0xcccccccc));
4    System.out.println("16进制0x33333333转化为二进制是----->" + Util.bitInt32(0x33333333));
5    System.out.println("16进制0xf0f0f0f0转化为二进制是----->" + Util.bitInt32(0xf0f0f0f0));
6    System.out.println("16进制0x0f0f0f0f转化为二进制是----->" + Util.bitInt32(0x0f0f0f0f));

再来看一下运行结果

1        16进制0xaaaaaaaa转化为二进制是----->10101010 10101010 10101010 10101010
2        16进制0x55555555转化为二进制是----->01010101 01010101 01010101 01010101
3        16进制0xcccccccc转化为二进制是----->11001100 11001100 11001100 11001100
4        16进制0x33333333转化为二进制是----->00110011 00110011 00110011 00110011
5        16进制0xf0f0f0f0转化为二进制是----->11110000 11110000 11110000 11110000
6        16进制0x0f0f0f0f转化为二进制是----->00001111 00001111 00001111 00001111

我们很容易发现规律,第一个是10循环,第二个是01循环,第3个是1100循环,第4个是0011循环……也就是上面图中分析的每2个,每4个,每8个……一组进行运算。


上面都是先运算之后再移位进行操作,其实我们还可以先移位之后再进行运算,我们看下代码
public int hammingWeight(int n) {
    n = ((n >>> 1) & 0x55555555) + (n & 0x55555555);
    n = ((n >>> 2) & 0x33333333) + (n & 0x33333333);
    n = (((n >>> 4) & 0x0f0f0f0f) + (n & 0x0f0f0f0f));
    n = n + (n >>> 8);
    n = n + (n >>> 16);
    return n & 63;
}

写法上虽然有差别,但整体思路还是一样。这里再提到一点,代码的最后为什么要和63进行与运算,这是因为在第5和6行也就是每8位和每16位运算的时候,前面已经计算过的数据没有过滤掉,而63的二进制表示是00111111(6个1)。如果我们把前面计算过的值过滤掉就不用再和63进行与运算了,我们就以第一种写法为例来写一下

public int bitCount(int n) {
    n = ((n & 0xaaaaaaaa) >>> 1) + (n & 0x55555555);
    n = ((n & 0xcccccccc) >>> 2) + (n & 0x33333333);
    n = (((n & 0xf0f0f0f0) >>> 4) + (n & 0x0f0f0f0f));
    n = (((n & 0xff00ff00) >>> 8) + (n & 0x00ff00ff));
    n = (((n & 0xffff0000) >>> 16) + (n & 0x0000ffff));
    return n;
}

我们来找几个数据测试一下,使用最后一个方法看一下运行结果

    int num = 100;
    System.out.println(num + " 的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
    num = 1024;
    System.out.println(num + "的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
    num = 0;
    System.out.println(num + "   的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
    num = -1;
    System.out.println(num + "  的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
    num = -2;
    System.out.println(num + "  的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
    num = -100;
    System.out.println(num + "的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));

我们直接看打印结果就行了

1        100 的二进制是----->00000000 00000000 00000000 01100100 ---->1的个数是:3
2        1024的二进制是----->00000000 00000000 00000100 00000000 ---->1的个数是:1
3        0   的二进制是----->00000000 00000000 00000000 00000000 ---->1的个数是:0
4        -1  的二进制是----->11111111 11111111 11111111 11111111 ---->1的个数是:32
5        -2  的二进制是----->11111111 11111111 11111111 11111110 ---->1的个数是:31
6        -100的二进制是----->11111111 11111111 11111111 10011100 ---->1的个数是:28

结果证明我们的写法是完全正确的。

2,每四位存储

上面我们讲了是每两位存储,那么我们能不能每4位存储呢,其实也是可以的,我们只需要在第一步计算的时候每4位中1的个数存储在这4位中,后面的代码就和上面一样了。明白了上面的解法,这种就非常简单了,图就不再画了,我们来看下代码

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

和上面不同的主要是在第2行,他是先把每4位中包含的1全部都统计一遍,然后再存储在这4位二进制位中,后面再进行计算。

3,每多位存储

上面我们分别采用了每两位存储,每4位存储,我们来头脑风暴一下,我们能不能每8位,每16位呢,当然也是可以的,原理和上面类似,只不过是在第一步统计1的个数的时候可能会麻烦些,代码就不再写了。我们再来思考一下,上面无论是每2位,4位,8位,还是16位,他们都能被32整除。我们能不能使用一个不能被32整除的数来存储呢,比如3,或者5,7……,其实只要不是超过32的正整数都是可以的,只不过使用一个不能被32整除的数计算可能稍微会麻烦些,我们就拿每3位存储来分析一下。

3明显不能被32整除,所以我们不能完全照着上面来做,我们可以这么来做,右边30位每3位分为一组存储1的个数,最左边2个是一组来存储1的个数,我们来看下代码怎么写

public static int bitCount(int n) {
    n = (n & 011111111111) + ((n >>> 1) & 011111111111) + ((n >>> 2) & 011111111111);
    n = ((n + (n >>> 3)) & 030707070707);
    n = ((n + (n >>> 6)) & 07700770077);
    n = ((n + (n >>> 12)) & 037700007777);
    return ((n + (n >>> 24))) & 63;
}

注意这里以0开头的数字是8进制的,我们来找几组数据再来测试一下

 1    int num = 100;
 2    System.out.println(num + " 的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
 3    num = Integer.MAX_VALUE;
 4    System.out.println(num + "的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
 5    num = 0;
 6    System.out.println(num + "   的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
 7    num = -1;
 8    System.out.println(num + "  的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
 9    num = -7;
10    System.out.println(num + "  的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
11    num = Integer.MIN_VALUE;
12    System.out.println(num + "的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));

直接来看运行结果

1        100 的二进制是----->00000000 00000000 00000000 01100100 ---->1的个数是:3
2        2147483647的二进制是----->01111111 11111111 11111111 11111111 ---->1的个数是:31
3        0   的二进制是----->00000000 00000000 00000000 00000000 ---->1的个数是:0
4        -1  的二进制是----->11111111 11111111 11111111 11111111 ---->1的个数是:32
5        -7  的二进制是----->11111111 11111111 11111111 11111001 ---->1的个数是:30
6        -2147483648的二进制是----->10000000 00000000 00000000 00000000 ---->1的个数是:1

结果完全完全在我们的预料之中。

我们看到每组的数量即使不能被32整除,我们依然可以计算,但上面的计算还有一个巧的地方,就是每3个一组的时候,可以用8进制的3个1来参与运算,如果每5,7,9……个一组能不能计算?当然也是可以的,下面我们以每5个一组来看一下怎么计算的。

public static int bitCount(int n) {
    n = (n & 0x42108421) + ((n >>> 1) & 0x42108421) + ((n >>> 2) & 0x42108421) + ((n >>> 3) & 0x42108421) + ((n >>> 4) & 0x42108421);
    n = ((n + (n >>> 5)) & 0xc1f07c1f);
    n = ((n + (n >>> 10) + (n >>> 20) + (n >>> 30)) & 63);
    return n;
}

我们再来找几组数据测试一下

 1    int num = 100000000;
 2    System.out.println(num + " 的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
 3    num = Integer.MAX_VALUE;
 4    System.out.println(num + "的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
 5    num = 0;
 6    System.out.println(num + "   的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
 7    num = -1;
 8    System.out.println(num + "  的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
 9    num = Integer.MAX_VALUE - 10000;
10    System.out.println(num + "  的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
11    num = Integer.MIN_VALUE;
12    System.out.println(num + "的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));

再来看一下结果

1        100000000 的二进制是----->00000101 11110101 11100001 00000000 ---->1的个数是:12
2        2147483647的二进制是----->01111111 11111111 11111111 11111111 ---->1的个数是:31
3        0   的二进制是----->00000000 00000000 00000000 00000000 ---->1的个数是:0
4        -1  的二进制是----->11111111 11111111 11111111 11111111 ---->1的个数是:32
5        2147473647  的二进制是----->01111111 11111111 11011000 11101111 ---->1的个数是:26
6        -2147483648的二进制是----->10000000 00000000 00000000 00000000 ---->1的个数是:1

4,总结

我们如果以2,3,4……32分为一组计算,那么这题的解法就非常多了,在加上之前讲的几种解法就更多了。如果搞懂了上面的代码,你会发现之前使用循环的方式统计简直弱爆了,这种写法在面试中我觉得会更有优势,但前提是面试官要懂。其实这题的解法还远远不止这些,后面我们还会再讲对这题的其他的一些算法。


在这里插入图片描述

posted @ 2020-09-24 21:50  数据结构和算法  阅读(163)  评论(0编辑  收藏  举报