劍指offer刷題記錄11——二進位制中1的個數
題目描述
輸入一個整數,輸出該數二進位制表示中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
方法總結:
&:位的與運算
<<:左移一位