我一直在做一个问题,其中我给了一个数字,让我们说5(101)没有任何连续的,我需要找到下一个没有连续的数字。
接下来是6(110),7(111),8(1000)。所以我的回答应该是8。
任何人都可以告诉我这种方法,除了转到下一个数字并看到连续位。
答案 0 :(得分:5)
解决此问题的一种快速方法是扫描数字的位,直到找到两个连续的1
。发生这种情况时,您需要修复它。换句话说,您想要使数字略大于当前数字。真的多大了多少?
考虑11
:
...11...
我们会说11...
是当前号码的后缀。想象一下,我们不断在后缀中添加1,直到11
消失。什么时候会发生?好吧,11
不会变成10
或01
,因为这会使它变小。我们只是在增加数量。
11
只会在后缀变为00...
时消失。最小的此类后缀由全零组成。因此,当我们遇到11
时,我们可以立即将其清零,并将其后的所有位清零。然后我们将1
添加到后缀左侧的位。
例如,请考虑此数字中最右边的11
:
1000101011000100
^^
suffix: 11000100
prefix: 10001010
我们将后缀清零并在前缀中添加一个:
suffix: 00000000
prefix: 10001011
result: 1000101100000000
现在我们继续向左搜索,寻找下一个11
。
以下函数向右移动后缀为零,在前缀中加1,向左移动以将前缀恢复到其位置。
int next(int x) { /* Look for a number bigger than x. */
x += 1;
int mask = 3, /* Use the mask to look for 11. */
pos = 2; /* Track our location in the bits. */
while (mask <= x) {
if ((mask & x) == mask) { /* If we find 11, shift right to */
x >>= pos; /* zero it out. */
x += 1; /* Add 1, shift back to the left, */
x <<= pos; /* and continue the search. */
}
mask <<= 1; /* Advance the mask (could advance */
pos += 1; /* another bit in the above case). */
}
return x;
}
这种方法对输入的每个位执行恒定的操作次数,使其比蛮力方法快得多。形式上,运行时间与输入的大小成对数。
下面是一个完整的程序,它在命令行中获取x
的值。
#include <stdlib.h>
#include <stdio.h>
void display(int x) {
int p = 1;
while (p < x) {
p <<= 1;
}
while (p != 0) {
printf("%d", (x & p) ? 1 : 0);
p >>= 1;
}
}
int next(int x) { /* Look for a number bigger than x. */
x += 1;
int mask = 3, /* Use the mask to look for 11. */
pos = 2; /* Track our location in the bits. */
while (mask <= x) {
if ((mask & x) == mask) { /* If we find 11, shift right to */
x >>= pos; /* zero it out. */
x += 1; /* Add 1, shift back to the left, */
x <<= pos; /* and continue the search. */
}
mask <<= 1; /* Advance the mask (could advance */
pos += 1; /* another bit in the above case). */
}
return x;
}
int main(int arg_num, char** args) {
int x, y;
if (arg_num != 2) {
printf("must specify a number\n");
return 0;
}
x = atoi(args[1]);
y = next(x);
printf("%d -> %d\n", x, y);
display(x);
printf(" -> ");
display(y);
printf("\n");
return 0;
}
答案 1 :(得分:2)
好的,我编译并测试了这个,所以我知道这很有用。首先,回答原始问题。它可以使用一些技巧在恒定时间内快速完成:
int getNext(int x)
{
int invx = ~x;
int dbl0 = invx & (invx >> 1);
dbl0 = (dbl0 & -dbl0);
x |= dbl0;
x &= ~(dbl0 - 1);
return x;
}
这背后的想法是你需要在数字中找到最右边的00模式。 00模式右侧的所有内容必须是10101010的形式...因为我们知道没有其他00模式,右边没有其他11个模式(给定数字没有任何连续的1)。一旦找到最右边的00模式,就可以将低0设置为1并将低于该值的所有其他位清零。例如:
1001010001010010101010
^ = lowest 00 pattern
1001010001010100000000
^ set this bit and clear all bits to its right
位技巧步骤如下:
1001010001010010101010 = x
0110101110101101010101 = ~x = invx (flip the bits because we are looking for 0s)
0010000110000100000000 = invx & (invx >> 1) = dbl0 (find all 00 patterns)
0000000000000100000000 = dbl0 & -dbl0 (isolate the lowest 00 pattern)
现在谈谈Michael Laszlo提出的一般性问题。只有在允许CLZ(计数前导零)内置时,才能在恒定时间内解决这个问题。否则,需要log(n)时间来隔离数字的高位。基本思路与以前一样。唯一的区别是,我们必须找到最高的00模式,而不是找到最低的00模式。换句话说,我们必须首先找到最高的11个模式,然后搜索高于00模式的位。
int getNextGeneral(int x)
{
int dbl0 = 0;
int dbl1 = (x & (x >> 1));
int invx = 0;
// Find the highest 11 pattern in x. If we find such a pattern,
// we write 1s to all bits below the 11 pattern. This is so that
// when we will be forced to find a 00 pattern above the highest
// 11 pattern.
if (dbl1 != 0) {
// Assumes 32 bit integers. Isolates high bit in dbl1.
dbl1 = 1 << (31 - __builtin_clz(dbl1));
// Write all 1s to x, below the highest 11 found.
x |= dbl1 - 1;
}
// This code is the same as before.
invx = ~x;
dbl0 = invx & (invx >> 1);
dbl0 = (dbl0 & -dbl0);
x |= dbl0;
x &= ~(dbl0 - 1);
return x;
}
答案 2 :(得分:0)
这是一种替代方法,基于与CLZ方法大致相同的思想,除了它从右到左而不是从左到右工作,它可能需要几个步骤。与发布的其他从右到左方法不同,它只访问问题网站,而不是遍历所有位。
注释中提到的公式x & (x >> 1)
会掩盖所有位为1且位于其左侧的1。因此,该掩码中最低的1位是“第一个问题”,我们可以解决如下问题:(选择uint
为某种无符号类型)
uint m = x & (x >> 1);
uint lowest = m & -m; // get first problem
uint newx = (x + lowest) & (lowest - 1); // solve it
最后一行增加11找到并将其右边的所有内容归零。
如果没有问题(即m = 0
),那么lowest = 0
也是如此,所以在最后一行中,没有添加任何东西,你和所有的一起(我宁愿称之为-1,但这是一个C问题,所以有人会抱怨这个)。简而言之,如果没有发生11,则数字不变。
如果我们可以在没有任何控制流程的情况下使用它,那本来是很好的,但是我们不能 - 修复一个问题可能会导致一个问题进一步向左(或者它可能已经存在)。所以这必须循环,直到没有更多问题,例如:(未经测试)
uint m = x & (x >> 1);
do {
uint lowest = m & -m;
x = (x + lowest) & (lowest - 1);
m = x & (x >> 1);
} while (m);
显然,它希望原始数字加1 作为输入,否则它可以返回相同的数字。
这是一种通过JS1在漂亮的CLZ方法中摆脱CLZ的方法。重写CLZ几乎总是会产生“后缀-OR”,这次确实会这样做。
关键是要隔离最高1然后减去1,这可以直接完成(即没有clz和shift),如下所示:(这里的循环称为“后缀-OR”)
cbl1 >>= 1;
for (int i = 1; i < sizeof(something) * CHAR_BIT; i <<= 1)
dbl1 |= dbl1 >> i;
当然不是一个真正的循环,如果不展开它将是愚蠢的(基本上它将在实践中进行5或6次迭代)。
你可以把它算作恒定时间,取决于你想要的东西 - 如果你想考虑你正在使用变量的类型的大小,那么这个东西需要O(log n)步。 / p>