查看原文
其他

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

山大王wld 数据结构和算法 2022-05-01

The two most important days in your life are the day you are born and the day you find out why.

你人生中有两个最重要的日子:一是你出生的日子,二是你发现自己存在意义的日子。


问题描述

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


1,每两位存储

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


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

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

这里以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个……一组进行运算。


上面都是先运算之后再移位进行操作,其实我们还可以先移位之后再进行运算,我们看下代码

1public int hammingWeight(int n) {
2    n = ((n >>> 1) & 0x55555555) + (n & 0x55555555);
3    n = ((n >>> 2) & 0x33333333) + (n & 0x33333333);
4    n = (((n >>> 4) & 0x0f0f0f0f) + (n & 0x0f0f0f0f));
5    n = n + (n >>> 8);
6    n = n + (n >>> 16);
7    return n & 63;
8}

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

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

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

1    int num = 100;
2    System.out.println(num + " 的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
3    num = 1024;
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 = -2;
10    System.out.println(num + "  的二进制是----->" + Util.bitInt32(num) + "---->1的个数是:" + bitCount(num));
11    num = -100;
12    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位中,后面的代码就和上面一样了。明白了上面的解法,这种就非常简单了,图就不再画了,我们来看下代码

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

和上面不同的主要是在第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的个数,我们来看下代码怎么写

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

注意这里以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个一组来看一下怎么计算的。

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

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

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



383,不使用“+”,“-”,“×”,“÷”实现四则运算

364,位1的个数系列(一)

361,交替位二进制数


长按上图,识别图中二维码之后即可关注。


如果喜欢这篇文章就点个"在看"吧

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存