輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。算法
解題前,咱們先來了解一下補碼。在計算機系統中,數值都是用補碼來表示和存儲的。
而原碼就是數值的二進制數表示,最高位1表示負數。
以32位數值舉例
1的原碼就是
測試
-1的原碼就是
優化
正數的補碼等於原碼
負數的補碼等於其原碼按位取反後(除了最高位)加1,好比-1的補碼就是32個1
3d
使用補碼的好處在於,能夠將符號位和數值位統一處理,加法與減法也能夠統一處理。
好比3 - 1
,等價於3 + (-1)
,則對於計算機來講將3和-1的補碼直接相加就能夠,計算過程以下圖所示。若是直接使用數值的原碼錶示,則不會獲得正確的結果,感興趣的同窗能夠計算試一下,這裏再也不贅述。code
對於本題,首先想到的是將二進制數一直右移,這樣的話該數的每一位都會依次成爲最低位,而後將該數和1相與,計算1的個數。(因爲1只有最低位是1,其餘位都是0,某個數和它相與後,結果若是是1,就說明該數最低位是1,不然就是0)
按照以上思想,實現的代碼以下,可是請注意,這樣的寫法是錯誤。沒有考慮負數的狀況,和正數右移最高位補0不一樣的是,負數右移最高位補1,這樣就會有無數個1,致使死循環。blog
public int NumberOf1(int n) { int count = 0; // 沒有考慮負數,一直不會等於0 while(n != 0) { if ((n & 1) == 1) count++; // 負數右移最高位補1 n >>= 1; } return count; }
既然將目標數右移和1與行不通,那麼咱們能夠反過來,將1不斷左移(從最低位到最高位每一位依次是1,其餘位是0),而後和目標數相與來求1的個數。具體過程以下圖所示
it
public int NumberOf1(int n) { int unit = 1, count = 0; while (unit != 0) { if ((unit & n) != 0) count++; unit <<= 1; } return count; }
上面解法1的時間複雜度是O(n的位數),n有所少位就要循環多少次。能夠利用一個小技巧,下降算法的時間複雜度。
先來看一個例子,對於任意一個數將其減1,好比7
7的補碼錶示是
class
(7的補碼)
減1後爲6,補碼錶示以下。若是再和7
相與,獲得的值仍爲6。獲得的值至關於把7
從右邊數的第一個1被變成了0
循環
(6的補碼)
好比6,再減1,爲5,補碼錶示以下
技巧
(5的補碼)
若是再和6
相與,獲得的值爲4。補碼錶示以下,獲得的值至關於把6
從右邊數的第一個1被變成了0
若是用負數進行測試,也是同樣的結果。
由此咱們能夠發現對於數值n,將n - 1後再和n相與,獲得的值至關於將n從右邊數的第一個1變成0。n的二進制表示中有多少個1,就能變多少次。實現代碼以下,時間複雜度優化爲O(n中1的個數)
public int NumberOf1(int n) { int count = 0; while (n != 0) { count++; n = (n - 1) & n; } return count; }