转自 http://blog.csdn.net/ligt0610/article/details/7262757
一般最容易想到的方法就是先计算正整数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的个数。