zoukankan      html  css  js  c++  java
  • 求比正整数N大的最小正整数M,且M与N的二进制表示中有相同数目的1

    转自 http://blog.csdn.net/ligt0610/article/details/7262757

    一般最容易想到的方法就是先计算正整数N用二进制表示时1的个数count1,然后不停地计算N++用二进制表示时1的个数count2,直到碰到count1 == count2成立,代码如下:

    [cpp] 
     
    1. typedef unsigned int uint;   
    2. //解法一:   
    3. uint count1Bits(uint n)   
    4. {   
    5.     uint count = 0;   
    6.     while (0 != n)   
    7.     {   
    8.         n &= n - 1;   
    9.         count++;   
    10.     }   
    11.     return count;   
    12. }   
    13.   
    14. uint getNextN_1(uint n)   
    15. {   
    16.     uint count = count1Bits(n);   
    17.     while (n++)   
    18.     {   
    19.         uint temp = count1Bits(n);   
    20.         if (temp == count)   
    21.         {   
    22.             break;   
    23.         }   
    24.     }   
    25.     return n;   
    26. }   

     

    接下来,看这样一种情况:

    比如正整数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。这种方法自然还是比较简单的,在这里介绍另外一种计算方法,先给出如下:

    [cpp] 
     
    1. //解法二:   
    2. uint getNextN_2(uint n)   
    3. {   
    4.     uint temp1 = n & (-n);   
    5.     uint temp2 = n + temp1;   
    6.     uint ret = temp2 | ((n ^ temp2) / temp1) >> 2;   
    7.     return ret;   
    8. }  

     

    接下来,我们对以上代码再进行解释:

    第一步,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的个数。

  • 相关阅读:
    Mockito测试
    linux笔记:shell编程-正则表达式
    linux笔记:shell基础-环境变量配置文件
    linux笔记:shell基础-bash变量
    linux笔记:shell基础-bash基本功能
    linux笔记:shell基础-概述和脚本执行方式
    linux笔记:文件系统管理-fdisk分区
    linux笔记:文件系统管理-分区、文件系统以及文件系统常用命令
    linux笔记:权限管理-sudo
    linux笔记:用户和用户组管理-用户管理命令
  • 原文地址:https://www.cnblogs.com/pengyu2003/p/3597369.html
Copyright © 2011-2022 走看看