目录

问题(也称汉明重量问题) 

方案1:遍历统计

方案2:& << 遍历的改进

方案3:挨个判断最后一位是否为1(只实用正整数)

方案4:实用N&(N-1) 替代 n % 2 获取尾部为1的big位

总结

扩展:汉民距离


问题(也称汉明重量问题) 

统计int数字存储二进制中的1个的个数

比如:(java int)

3,二进制:11,out:2

-3,二进制:11111111111111111111111111111101,out:31

这个问题 叫做 汉明重量( 是一串符号中非零符号的个数)

方案1:遍历统计

1. 十进制的int转换为二进制的String

2. String 转换为char[]

3. 遍历数组,统计1的个数

public int hammingWeight1(int n) {
	    	String str = Integer.toBinaryString( n);
	    	char[] chars = str.toCharArray();
	    	int count = 0 ;
	    	for(char ch :chars){
	    		if(ch == '1'){
	    			count ++;
	    		}
	    	}
			return count;
	    }

空间是O(N),当然没有这么简单,这代码多low。针对二进制运算,一般都需要用位运算符号,比如:& | ! ^异或 以及移位: >>  << 。如果忘记可参考:

方案2:& << 遍历的改进

方案1中有转换为二进制串过程,按个遍历每个字符。如果一个计算,能从效果上判断二进制位置是否为1,那么就不需要转二进制串这个过程了。 答案就是:按位与&,而迭代量,分别是二进制:1,10,100,1000,1000 … 这不就是左移?测试代码如下,理解下:与 和 左移

11111111111111111111111111110101 ---> 十进制 -11的二进制补码
                              10
                             100
                            1000
                           10000
                          100000
                         1000000
                        10000000
                       100000000
                      1000000000
                     10000000000
                    100000000000
                   1000000000000
                  10000000000000
                 100000000000000
                1000000000000000
               10000000000000000
              100000000000000000
             1000000000000000000
            10000000000000000000
           100000000000000000000
          1000000000000000000000
         10000000000000000000000
        100000000000000000000000
       1000000000000000000000000
      10000000000000000000000000
     100000000000000000000000000
    1000000000000000000000000000
   10000000000000000000000000000
  100000000000000000000000000000
 1000000000000000000000000000000
10000000000000000000000000000000
                               1
----------------------------------------------

按位与:如果没有1则是0,否则 != 0,这个会作为条件的。

                            1011
                             100
----------------------------------------------
                               0 (十进制:0)

                            1011
                            1000
----------------------------------------------
                            1000 (十进制:8)

代码:
		System.out.println(String.format("%32s", Integer.toBinaryString(-11)));
		for(int i = 1; i <= 32;i++){
			System.out.println(String.format("%32s", Integer.toBinaryString(1 << i)));
		}
		System.out.println("----------------------------------------------");

		System.out.println("");
		System.out.println(String.format("%32s", Integer.toBinaryString(11)));
		System.out.println(String.format("%32s", Integer.toBinaryString(1 << 2)));
		System.out.println("----------------------------------------------");
		System.out.println(String.format("%32s (十进制:%s)", Integer.toBinaryString(11 & (1 << 2)),(11 & (1 << 2))));
		
		
		System.out.println("");
		System.out.println(String.format("%32s", Integer.toBinaryString(11)));
		System.out.println(String.format("%32s", Integer.toBinaryString(1 << 3)));
		System.out.println("----------------------------------------------");
		System.out.println(String.format("%32s (十进制:%s)", Integer.toBinaryString(11 & (1 << 3)),(11 & (1 << 3))));

代码:

public class hammingWeight {
	
	@Test
	public void test(){
		
		System.out.println(String.format("%32s", Integer.toBinaryString(11)));
		Assert.assertEquals(3, new Solution().hammingWeight(11));
		System.out.println("");

		System.out.println(String.format("%2s - %32s - %6s", "序号","     二进制","与 结果"));
		System.out.println(String.format("%2s - %32s - %6s", "",Integer.toBinaryString(-11),"(十进制 -11)"));
		Assert.assertEquals(30, new Solution().hammingWeight(-11));
		
	}
	
	public class Solution {
		
		public int hammingWeight(int n) {
	    	int count = 0 ;
	    	int i = 1;
	    	// java int 有4个字节32位,遍历32次
	    	int len = 0; 
	    	while(len++ < 32){
	    		System.out.println(String.format("%2s - %32s - %6s", len,Integer.toBinaryString(i),(i & n)));

	    		// 查看第1位置, 条件不是  ==1,而应该是 !=0, 可以看结果
	    		if((i & n) != 0){
	    			count ++;
	    		}
	    		
	    		// i左移1位
	    		i = i << 1;
	    		
	    	}
			return count;
	    }
  }

测试结果:

序号 -                              二进制 -   与 结果
   - 11111111111111111111111111110101 - (十进制 -11)
 1 -                                1 -      1
 2 -                               10 -      0
 3 -                              100 -      4
 4 -                             1000 -      0
 5 -                            10000 -     16
 6 -                           100000 -     32
 7 -                          1000000 -     64
 8 -                         10000000 -    128
 9 -                        100000000 -    256
10 -                       1000000000 -    512
11 -                      10000000000 -   1024
12 -                     100000000000 -   2048
13 -                    1000000000000 -   4096
14 -                   10000000000000 -   8192
15 -                  100000000000000 -  16384
16 -                 1000000000000000 -  32768
17 -                10000000000000000 -  65536
18 -               100000000000000000 - 131072
19 -              1000000000000000000 - 262144
20 -             10000000000000000000 - 524288
21 -            100000000000000000000 - 1048576
22 -           1000000000000000000000 - 2097152
23 -          10000000000000000000000 - 4194304
24 -         100000000000000000000000 - 8388608
25 -        1000000000000000000000000 - 16777216
26 -       10000000000000000000000000 - 33554432
27 -      100000000000000000000000000 - 67108864
28 -     1000000000000000000000000000 - 134217728
29 -    10000000000000000000000000000 - 268435456
30 -   100000000000000000000000000000 - 536870912
31 -  1000000000000000000000000000000 - 1073741824
32 - 10000000000000000000000000000000 - -2147483648

方案3:挨个判断最后一位是否为1(只实用正整数)

101   --  最后一位1 
10  -- 最后一位 0
1  -- 最后一位 1

从101 到 10 ,在到 1。 实用 右移 >> 实现

注意:(假设用8bit位置存储整数)
1. 正数101,其实是:0000 0101 ,右移前面补充0
2. 负数-3,存储的是补码:1111 1101 ,右移前面补充1,就会导致1没完没了
public int hammingWeight2(int n) {
	    	int count = 0 ;
	    	if(n < 0){
	    		return hammingWeight1(n);
	    	}
	    	while(n != 0){
	    		count += n % 2;
	    		n >>= 1;
	    	}
			return count;
	    }

方案4:实用N&(N-1) 替代 n % 2 获取尾部为1的big位

方案2和方案3 都是每次只看最后1位,判断是否为1。 或者编写逻辑。这样中间多判断为0的操作,有没有操作能直接数1的个数。

原理是:6&(6-1) = 4, 6 = (110), 4 = (100)  ,运算结果 44 即为把 66 的二进制位中的最低位的 1 变为 0 之后的结果。(即:Brian Kernighan 算法)

测试验证:

16    10000
15     1111 |  16 & 15 =  0   -- 不就是把 10000 最后一个1 去掉为:00000
14     1110 |  15 & 14 = 14   -- 14 不就是15=1111去掉最后一个1,就是1110
13     1101 |  14 & 13 = 12   -- 类似
12     1100 |  13 & 12 = 12
11     1011 |  12 & 11 =  8
10     1010 |  11 & 10 = 10
 9     1001 |  10 &  9 =  8
 8     1000 |   9 &  8 =  8
 7      111 |   8 &  7 =  0
 6      110 |   7 &  6 =  6
 5      101 |   6 &  5 =  4
 4      100 |   5 &  4 =  4
 3       11 |   4 &  3 =  0
 2       10 |   3 &  2 =  2
 1        1 |   2 &  1 =  0

@Test
public void test1(){
	System.out.println(String.format("%2s %8s",16, Integer.toBinaryString(16)));
	for(int i = 15; i >= 1;i--){
		System.out.println(String.format("%2s %8s |  %2s & %2s =%3s",i, Integer.toBinaryString(i),i+1,i,i & (i+1)));
	}
}

最终代码:

public int hammingWeight(int n) {
	int count = 0 ;
	while(n != 0){
		// 去掉尾部一个1,并将去掉尾部1的数值 继续赋值给n
		n = n & (n-1);
		count ++;
	}
	return count;
}

总结

1.  计算效率最高,其次再想数据结构解决问题

2. 涉及单个数字问题,求二进制位相关问题,站在十进制角度,就只有一个数字,但如果站在二进制角度,就是一个bit的数组,二进制操作可以通过:& | ! ^异或 以及移位: >>  <<  实现移动

3. 方案都是慢慢递进,比如本次问题,很容易就想到转换为数组遍历统计,其次才是对这种思想的变形,通过二进制运算实现遍历的效果。有了遍历和二进制的思想,就容易想到%2统计尾部的bit位是0 1, 统计了 01 就会统计了一部分没用的0,这时如果没有见过 n ^ n-1 结果是 n 最后一位1去掉的结果。

4. 每种方案的递进改进,是新的工具引入的结果。所以如果想不到新的方案,一是可能思想不到位,第二就是你见过的工具就不先进,自然就不会想到新的方案。这点结论不知实用计算机领域的问题解决。

扩展:汉民距离

汉明距离:是 两个等长字符串 对应位置的不同字符的个数。 很容易想到异或,然后统计汉明重量。java中有汉明重量的现成方法:Integer.bitCount(x ^ y)

Java 移位不带符号_左移右移

 代码可以放在力扣执行验证: 

class Solution {
		
		public int hammingDistance3(int x, int y) {

			return Integer.bitCount(x ^ y);
		}

		public int hammingDistance2(int x, int y) {
			 return hammingWeight(x ^ y);
		}
		
		public int hammingWeight(int n) {
			int count = 0 ;
			while(n != 0){
				// 去掉尾部一个1,并将去掉尾部1的数值 继续赋值给n
				n = n & (n-1);
				count ++;
			}
			return count;
		}
		
		public int hammingDistance1(int x, int y) {

			char[] charsx = String.format("%32s", Integer.toBinaryString(x)).toCharArray();
			char[] charsy = String.format("%32s", Integer.toBinaryString(y)).toCharArray();
			int count = 0 ;
			for(int i = 0 ; i < 32;i++){
				charsx[i] = charsx[i] == ' ' ? '0' : charsx[i];
				charsy[i] = charsy[i] == ' ' ? '0' : charsy[i];
				if(charsx[i] != charsy[i]){
					count ++;
				}
			}
			return count;
		}
	}

end