天天看點

【劍指offer之二進制中1的個數】

【題目描述】:

請實作一個函數,輸入一個整數,輸出該數二進制表示中1的個數。

【思路1】可能引起死循環的解法

這是一道很基本的考查二進制和位運算的面試題。題目不是很難,面試官提出問題之後,我們很快就能形成一個基本的思路:先判斷整數二進制表示中最右邊一位是不是1.接着把輸入的整數右移一位,此時原來處于從右邊數起的第二位被移到最右邊了,在判斷是否為1.這樣每次移動一位,直到整個整數變成0為止。問題變成如何判斷一個整數的最右邊是否為1:不難想到,隻需把整數和1做與運算看結果是否為0就知道了。如果一個整數與1做與運算的結果為1表示該整數最右邊是1,否則為0.基于如此思路,代碼如下:

int number_of_one(int n)
{
    int count=0;
    while(n){
        if(n & 1) ++count;
        n >>= 1;
    }
    return count;
}      

面試官看了代碼之後可能會問:把整數右移一位和把整數除以2在數學上是等價的,那上面的代碼中可以把右移運算換成除2運算?答案是否定的。因為除法的效率比移位運算要低得多,在實際程式設計中應該盡可能地用移位運算符代替乘除法。

面試官接下來可能要問的第二個問題就是:上面的函數如果輸入一個負數,比如0x80000000,運作的時候會發生什麼情況?把負數0x80000000右移一位 的時候,并不是簡單地把最高位的1移到第二位變成0x40000000,而是0xC0000000。這是因為移位前是負數,仍然要保持移位後是負數,是以移位後的最高位仍然為1,如果一直做右移運算,最終這個數字會變成0xFFFFFFF而陷入死循環。

【思路2】正常解法

為了避免死循環,我們可以不右移輸入的數字n。首先把n和1做與運算,判斷n的最低位是不是1,接着把1左移一位得到2 ,再和n做與運算,就能判斷n的次第位是不是為1.。。

反複左移運算,每次都能判斷n的其中一位是不是1。此種解法的次數等于整數二進制的位數,32位的整數需要循環32此,基于此思路,代碼修改如下:

int number_of_one(int n)
{
    int count = 0;
    unsigned int flag = 1;
    while(flag){
        if(n & flag)
            ++count ;
        flag <<= 1;
    }
    return count;
}      

【思路3】

在分析這種算法之前,我們先來分析把一個數減去1的情況。如果一個整數不等于0,那麼該整數的二進制表示中至少一位是1。先假設這個數的最右邊一位為1,那麼減去1時,

最後一位變成0而其他所有位都保持不變。也就是最後一位相當于做了取反操作,由1變成了0。

接下來假設最後一位不是1而是0的情況。如果該整數的二進制表示中最右邊1位于第m位,那麼減去1時,第m位由1變成0,而第m位之後的所有0都變成1,整數中第m位之前的所有位都保持不變。舉個例子:

一個二進制數1100,它的第二位是從最右邊數起的一個1。減去1後,變成1011

在前面的兩種情況中,我們發現把一個整數減去1,都是把最右邊的1變成0。如果它的右邊還有0的話,所有的0都變成1,而它左邊的所有位都保持不變。接下來我們把一個整數和它減去1的結果做位與運算,相當于把它最右邊的1變成0。還是以前面的1100為例子,它減去1的結果是1011。二者做位與運算,得到的結果是1000,我們把1100最右邊的1變成了0結果正好是1000。

把上面的分析總結:把一個整數減去1,再和原整數做與運算,會把該整數最右邊的一個1

變成0。那麼一個整數的二進制表示中有多少個1,就可進行多少次這樣的操作。基于如此思路,代碼如下:

int number_of_one(int n)
{
    int count=0;
    while(n){
        ++count;
        n &= (n-1);
    }
    return count;
}      

牛客網版本:

class Solution {
public:
     int  NumberOf1(int n) {
         int ans=0;
         while(n){
             ++ans;
             n &=(n-1);
         }
         return ans;
     }
};      

繼續閱讀