求比正整數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的個數。