題目描述
輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。
將輸入的整數分成0,正,負三種情況分別討論。正數和0不必多說,當負數時,要將反碼的32位用0補全,補碼是取反加一,再求1的個數,所以可以不必求補碼,直接轉化成反碼加0,1和0的代數關係進行調換,即1 + 0 = 0, 0 + 0進一位0原位寫1,最終求0的個數,代碼如下:
解法一:
public class Solution {
public int NumberOf1(int n) {
if(n == 0) {
return 0;
}
if(n > 0) {
return NumberOf1(n / 2) + n % 2;
}
int num = 0;
if(n < 0) {
if(n == Integer.MIN_VALUE) {
return 1;
}
n = -n;
StringBuffer sb = new StringBuffer();
int flag = 1;
while(n > 0) {
sb.append(n % 2);
n /= 2;
}
while(sb.length() < 32) {
sb.append("0");
}
for(char c : sb.toString().toCharArray()) {
if(c == '0') {
if(flag == 0) {
num++;
}
else {
flag = 1;
}
}
else {
if(flag == 1) {
num++;
flag = 0;
}
}
}
}
return num;
}
}
運行時間:16ms
佔用內存:9404k
要記得考慮到當整數位Integer.MIN_VALUE時,其補碼爲1加上31個0,直接返回1即可。
不過想來這種解法實在繁瑣,進行了整數轉化爲二進制,再進行二進制的計算兩步,但是否可以直接用整數的二進制進行位運算呢,於是有了如下的解法。
解法二:遞歸
public class Solution {
public int NumberOf1(int n) {
if(n == 0) {
return 0;
}
return NumberOf1(n & (n - 1)) + 1;
}
}
在二進制的位面看待這些數,n - 1即是找到最後面的一個1,並將其置0,這個1後面的0全部置1,結果與n相與後剛剛置爲1的那些位又重新變成0,即n & (n - 1)是將n的最後一個1找到並置0,其他位置不變,那麼這個操作可以遞歸調用多少次,n的二進制表示就有多少個1。
運行時間:14ms
佔用內存:9288k
然而遞歸的實現是通過調用函數本身,函數調用的時候,每次調用時要做地址保存,參數傳遞等,這是通過一個遞歸工作棧實現的。具體是每次調用函數本身要保存的內容包括:局部變量、形參、調用函數地址、返回值。那麼,如果遞歸調用N次,就要分配N*局部變量、N*形參、N*調用函數地址、N*返回值。這勢必是影響效率的。
以上文字引用自其他人的博客 。
因爲遞歸使得效率低於普通的循環調用,簡單問題能不用遞歸就不用遞歸,將解法二進行改進↓
解法二改進:循環
public class Solution {
public int NumberOf1(int n) {
int res = 0;
while(n != 0){
res++;
n = n & (n - 1);
}
return res;
}
}
運行時間:12ms
佔用內存:9272k
繼續利用位運算,題目要求找到有多少個1,那麼想到設置一個mask,這個mask的二進制表示只有一個1,且這個1從最低位一直移動到最高位,每次移動之後與n進行與運算,結果不爲0的次數就是所求1的個數。
解法三:
public class Solution {
public int NumberOf1(int n) {
int res = 0;
int mask = 1;
for(int i = 0; i < 32; i++) {
if((mask & n) != 0) {
res++;
}
mask <<= 1;
}
return res;
}
}
運行時間:12ms
佔用內存:9280k
方法總結:
&:位的與運算
<<:左移一位