題目描述:
輸入一個32位整數,輸出該數二進制表示中1的個數。
注意:
- 負數在計算機中用其絕對值的補碼來表示。
樣例1
輸入:9
輸出:2
解釋:9的二進制表示是1001,一共有2個1。
樣例2
輸入:-2
輸出:31
解釋:-2在計算機裡會被表示成11111111111111111111111111111110,
一共有31個1。
分析:
方法一:
對負數單獨處理,我們知道,負數的補碼等于其原碼各位取反加1,或者說,是其原碼最後一個1之前所有的數都反轉過來,比如-2的補碼,是由-2的原碼除了後兩位及符号位不變,其它位均取反得到,也可以是2的原碼除了後兩位前面所有位取反得到。
這意味着求負數的補碼,我們隻需要找到其相反數的原碼最後一個1的位置即可,容易想到樹狀數組裡的lowbit運算,也就是n&(-n),設x的二進制表示中有ans個1,且其最後一個1在第p位(自右向左),則-x中1的位數為32-p-(ans-1)+1=34-p-ans。(仔細思考可了解,這裡不再贅述)。是以我們隻要對負數情況先做個标記求出p,然後對n取相反數,之後按照正數一樣的求,最後根據标記再來判斷最終結果即可。
需要注意的是對應10000...(31個0),表示的是-2147483648,也就是32位整數補碼中比原碼多表示的那個數。如果我們對其取相反數,得到的數會超過32位,引起錯誤,是以單獨判斷一下即可。代碼如下:
class Solution {
public:
int NumberOf1(int n) {
int ans = 0,p = 0;
bool flag = false;
if(n < 0){
if(n == -2147483648) return 1;
flag = true;
int k = n & (-1)*n;
while(k > 0){
k >>= 1;
p++;
}
n = (-1) * n;
}
while(n > 0){
if(n & 1) ans++;
n >>= 1;
}
if(flag) ans = 34 - p - ans;
return ans;
}
};
方法二:
如果說方法一是讓我們加深對原碼補碼的了解,那麼方法二就是利用了強制類型轉換。在計算機組成原理中資料表示與運算一章,我們可以看見:強制類型轉換并不改變變量的存儲内容,而隻是改變其解釋方式。
回到問題起點,我們為什麼要單獨對負數單獨處理,那是因為計算1個數的循環裡,判斷條件是n大于0,是否可以将判斷條件改為n不等于0,以此來對正數負數統一判斷?對于正數肯定是可以的,對于負數想象着每次右移也更加接近0了,其實不然,這樣對負數每次右移一位,左端會補符号位,也就是一直向右移,符号位始終是1,進而造成死循環。
為了避免死循環,我們可以将n強轉為無符号類型,這樣一來,右移時左邊隻會補0,便可以統一處理了。
class Solution {
public:
int NumberOf1(int n) {
int ans = 0;
unsigned int un = n;
while(un > 0){
if(un & 1) ans++;
un >>= 1;
}
return ans;
}
};