求比正整数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的个数。





  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值