題目描述:
輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼表示。
分析:
先復習幾個知識點:
- 補碼: 在計算機中所有的整數都是以二進制形式存儲的,其中正整數用原碼表示,負數用其正值的補碼表示。
補碼的計算機方式:對該負數的正值的原碼,進行取反后加一,得到補碼。
例如-6在計算機中的補碼表示如下:(以32位計算機為例)
-6 的正值 6 的二進制:
00000000 00000000 00000000 00000110
取反,得反碼:
11111111 11111111 11111111 11111001
+1 ,得補碼,即 -6 在計算機中的二進制表示:
11111111 11111111 11111111 11111010
- 補碼的性質:在計算機中,加法器實現最為簡單,很多運算最終都要轉化為加法運算,而,減去一個數相當于加上這個數的補碼。(非常巧妙的設計,讓人佩服馮諾依曼的天才創意!關于補碼的詳細介紹)
負數的右移:因為負數在內存中是以補碼形式存在的,所有首先根據負數的原碼求出負數的補碼(符號位不變,其余位按照原碼取反加1),然后保證符號位不變,其余位向右移動到X位,在移動的過程中,高位補1.等移位完成以后,然后保持符號位不變,其余按位取反加1,得到移位后所對應數的原碼。即為所求。詳見
正數的左移與右移、負數的無符號右移,就是相應的補碼移位所得,在高位補0即可。
負數的右移,就是補碼高位補1,然后按位取反加1。左移、右移:
在不溢出的情況下
左移n位后的值 等于原值乘以2的n次方
例如 4 <<2 就是16,二進制就是 00000100 <<00010000
-4<<2 就是-16 二進制就是 11111100 <<11110000
右移n位后的值 等于原值除以2的n次方的商
例如 4 >>2 就是1,二進制就是 00000100 >>00000001
-4>>2 就是-1 二進制就是 11111100 <<11111111
代碼:
- 將二進制轉化為數組,然后一位一位進行判斷(重點:熟悉java中相應的API)
public class Solution {
public int NumberOf1(int n) {
int result =0;
char[] array = Integer.toBinaryString(n).toCharArray();
for(char c:array){
if(c=='1'){
result ++;
}
}
return result;
}
}
- 改進,既然熟悉java的API,就會發現JAVA 的 JDK 庫里 Integer 有個 bitCount 方法,直接調用即可:
public class Solution {
public int NumberOf1(int n) {
return Integer.bitCount(n);
}
}
查看Integer.bitCount()的源碼如下:
/**
* Returns the number of one-bits in the two's complement binary
* representation of the specified {@code int} value. This function is
* sometimes referred to as the <i>population count</i>.
*
* @return the number of one-bits in the two's complement binary
* representation of the specified {@code int} value.
* @since 1.5
*/
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;
}
二分法,兩兩一組相加,之后四個四個一組相加,接著八個八個,最后就得到各位之和了。
第一行是計算每兩位中的 1 的個數 , 并且用該對應的兩位來存儲這個個數 , 如 : 01101100 -> 01011000 , 即先把前者每兩位分段 01 10 11 00 , 分別有 1 1 2 0 個 1, 用兩位二進制數表示為 01 01 10 00, 合起來為 01011000.
第二行是計算每四位中的 1 的個數 , 并且用該對應的四位來存儲這個個數 . 如 : 01101100 經過第一行計算后得 01011000 , 然后把 01011000 每四位分段成 0101 1000 , 段內移位相加 : 前段 01+01 =10 , 后段 10+00=10, 分別用四位二進制數表示為 0010 0010, 合起來為 00100010 .
下面的各行以此類推 , 分別計算每 8 位 ,16 位 ,32 位中的 1 的個數 .
將 0x55555555, 0x33333333, 0x0f0f0f0f 寫成二進制數的形式就容易明白了 .
3.根據上面的源碼,可以類似的使用二分法寫出 HAKMEM 算法的解法
public class Solution {
public int NumberOf1(int n) {
int num;
num = (n >> 1) & 033333333333;
n = n - num;
num = (num >> 1) & 033333333333;
n = n - num;
n = (n + (n >> 3)) & 030707070707;
n = n%63;
return n ;
}
}
這個通過率只有40%,應該是哪里有問題。
- 通過位移判斷奇偶數并計數,標志位初始為1,將其和輸入值相與,n & 1 的結果為 1 或 0 ,為 1 的時候 count+=1 ,為 0 的時候 count+=0
public class Solution {
public int NumberOf1(int n) {
int count = 0;
int flag = 1;
while(flag!=0){
if((n&flag)!=0){
count++;
}
flag = flag<<1;
}
return count;
}
}
- 正整數的二進制數最高位為 0 ,負整數和二進制數最高們為 1 ,則可利用左移、判斷正負來實現 1 的個數的計算。
public class Solution {
public int NumberOf1(int n) {
int count = 0;
while(n!=0){
if(n<0){
count++;}
n=n<<1;//左移一位,左邊的最高位為符號位,根據正負數來判斷符號位的0,1,從而得到1的個數
}
return count;
}
}
這種方法最容易理解.
- 思想: x & (x-1) 可以消去 x 二進制數的最后一位 1
n : 10110100
n-1 : 10110011
n&(n-1) : 10110000
public class Solution {
public int NumberOf1(int n) {
int cnt = 0;
while (n != 0) {
cnt++;
n &= (n - 1);
}
return cnt;
}
}
把一個整數減去1,再和原整數做運算,會把該整數最右邊一個1變成0,那么一個整數的二進制表示有多少個1,就可以進行多少次這樣的操作.
O(logM) 時間復雜度解法,其中 M 表示 1 的個數。