1. 程式人生 > >求比正整數N大的最小正整數M,且M與N的二進位制表示中有相同數目的1

求比正整數N大的最小正整數M,且M與N的二進位制表示中有相同數目的1

一般最容易想到的方法就是先計算正整數N用二進位制表示時1的個數count1,然後不停地計算N++用二進位制表示時1的個數count2,直到碰到count1 == count2成立,程式碼如下:

typedef unsigned int uint; 
//解法一: 
uint count1Bits(uint n) 
{ 
    uint count = 0; 
    while (0 != n) 
    { 
        n &= n - 1; 
        count++; 
    } 
    return count; 
} 

uint getNextN_1(uint n) 
{ 
    uint count = count1Bits(n); 
    while (n++) 
    { 
        uint temp = count1Bits(n); 
        if (temp == count) 
        { 
            break; 
        } 
    } 
    return n; 
} 

接下來,看這樣一種情況:

比如正整數N為108,用二進位制表示為110 1100,比較容易求得比108大,且用二進位制表示時1的個數跟108相同的最小正整數為113,用二進位制表示為111 0001。為了方便觀察,把108與113對應的二進位制表示按以下方式排列:

108 :110 1100

113 :111 0001

通過以上的轉換可以看出,為了計算出113,只需要對108的二進位制中最右邊連續的1位串進行操作即可。操作過程描述如下:將連續的1位串中最左邊的1向左移動一位,其他的1位串移動到最右邊。這樣就保證了計算出來的數二進位制表示時1的個數跟原來相同,同時也比原來數大,並且是最小的。

通過將108先轉化為二進位制“1101100”字串,然後利用以上方法移動最右邊連續的1位串,最後把移動後的二進位制“1110001”字串轉化為整數113。這種方法自然還是比較簡單的,在這裡介紹另外一種計算方法,先給出如下:

//解法二: 
uint getNextN_2(uint n) 
{ 
    uint temp1 = n & (-n); 
    uint temp2 = n + temp1; 
    uint ret = temp2 | ((n ^ temp2) / temp1) >> 2; 
    return ret; 
}

接下來,我們對以上程式碼再進行解釋:

第一步,uint temp1 = n & (-n);它的功能是找到N(108)的二進位制表示中最右邊的1(這個1必定是N的二進位制表示中最右邊的連續的1位串的開始)。該過程如下:

n 110 1100

&

-n 001 0100

temp1 = n & (-n) 000 0100

第二步,uint temp2 = n + temp1;它實現了“將連續的1位串中最左邊的1向左移動一位”的功能,但是它也帶來了一個副作用:將連續的1位串中其他的1丟失了!其過程如下:

n 110 1100

+

temp1 000 0100

temp2 = n + temp1 111 0000

第三步,uint ret = temp2 | ((n ^ temp2) / temp1) >> 2;將第二步計算過程中丟失的1補上,並放到最右邊。首先,比較容易看出:需要補上的1的個數等於N的二進位制表示中最右邊的連續的1位串中1的個數減1,然而如何通過位操作來求得呢?這就是(n ^ temp2)的功能了,如以下過程所示,(n ^ temp2)的二進位制表示只包含1個連續的1串,並且1的個數正好等於N的二進位制表示中最右邊的連續的1位串中1的個數加1:

n 110 1100

^

temp2 111 0000

n ^ temp2 001 1100

由上面的分析可知,(n ^ temp2)中的1的個數實際上比我們需要補的1的個數多2。進步一分析得知,(n ^ temp2)的二進位制表示中最低位的1正好與temp1中那個1對應,因此我們可以通過((n ^ temp2) / temp1)將這些1全部移到最右邊,然後把計算結果右移2位(去掉多餘的2個1),即((n ^ temp2) / temp1) >> 2。這樣要補的1的個數及位置就全部計算完畢,如一下過程所示:

n ^ temp2 001 1100

/

temp1 000 0100

=

((n ^ temp2) / temp1) 000 0111

>>

2

=

((n ^ temp2) / temp1) >> 2 000 0001

|

temp2 111 0000

=

temp2 | ((n ^ temp2) / temp1) >> 2 111 0001

通過以上三步計算,就計算出比108(1101100)大且二進位制表示時1的個數相同的最小正整數是113(1110001)。第二種方法雖然理解起來不直觀,但優點是顯而易見的,演算法複雜度是O(1)。當n比較大時,第一種效率可能就會很低。比如n為2^31 - 1(01111111 11111111 11111111 11111111),則比n大且二進位制表示時1的個數相同的最小正整數是2^31 + 2^30 - 1(10111111 11111111 11111111 11111111),兩者相差2^30,也就是得迴圈2^30,並且每次迴圈過程中得計算該數用二進位制表示時1的個數。