位操作-给定一个二进制数,在“1”的个数保持不变的前提下,获取最近的略大数
首先考虑后一个数是什么样子的,看下边这个例子:
1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
如上表所示,如果我们想在保持1的个数不变的前提下,得到略大的数:
我们会发现,
1).给定一个数n,和两个位置i和j;假设将位i从0变到1,位j从1变到0,如果i再j的左侧,则变大
2).有一个0翻转至1,则必须有一个1翻转至0;
3).如果想让数变大,但是不至于太大,那么我们需要翻转最右侧的0,但是其右侧必须还要有1,换句话说,翻转最右侧但是不是拖尾的0,就上述表格中的数据而言,我们需要反转的是7所在的位置的0;
接下来是详细步骤:
步骤一:
翻转最右侧,但是非拖尾的0
首先根据获取到的P的位置(带翻转的0的位置),将其变为1;
步骤二:
将P右侧的所有位变为0
具体做法:
a=a<<p;//除去P为1,其余位均为0;
b=a-1;//将P位置之后的全部都设置成1;
mask=~b;//将P之前的所有位置均设置为1,其余变成0;
n&=mask;
上述等效为:
n&=~((1<<p)-1);
步骤三:
进行回填 将检测到的P位置之后的1的个数c1进行回填c1-1个1;
具体做法是:
a=1<<(c1-1);//将位c1-1设置成1,其余设置成0;
b=a-1;
n|=b;
简写为:
n|=(1<<(c1-1)-1);
整体的代码实现为:
1 int getNext(int n){ 2 int c=n; 3 int c0=0; 4 int c1=0; 5 while(((c&1)==0)&&(c!=0)){ 6 c0++;//获取位尾0的个数 7 c>>=1; 8 } 9 while((c&1)==1){ 10 c1++;//获取位尾1的个数 11 c>>=1; 12 } 13 if(c0+c1==31||c0+c1==0){ 14 return -1; 15 } 16 int p=c0+c1; 17 n|=(1<<p); 18 n&=~((1<<p)-1); 19 n|=(1<<(c1-1))-1; 20 return n; 21 }