385,位1的个数系列(二)
作者:互联网
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分为一组计算,那么这题的解法就非常多了,在加上之前讲的几种解法就更多了。如果搞懂了上面的代码,你会发现之前使用循环的方式统计简直弱爆了,这种写法在面试中我觉得会更有优势,但前提是面试官要懂。其实这题的解法还远远不止这些,后面我们还会再讲对这题的其他的一些算法。
长按上图,识别图中二维码之后即可关注。
如果喜欢这篇文章就点个"在看"吧
标签:11111111,系列,二进制,个数,----,num,-----,385 来源: https://blog.51cto.com/u_4774266/2902808