繁体   English   中英

获取下一个非连续的二进制数

[英]Get next non consecutive ones binary number

我一直在做一个问题,其中我给了一个数字,让我们说5(101)没有任何连续的,我需要找到下一个没有连续的数字。

接下来是6(110),7(111),8(1000)。 所以我的回答应该是8。

任何人都可以告诉我这种方法,除了转到下一个数字并看到连续的位。

解决此问题的一种快速方法是扫描数字的位,直到找到两个连续的1秒。 发生这种情况时,您需要修复它。 换句话说,您想要使数字略大于当前数字。 究竟要大多少?

考虑一下11两侧的位:

...11...

我们说11...是当前数字的后缀 想象一下,我们继续在后缀中添加1,直到11消失为止。 什么时候会发生? 那么, 11不会变成1001 ,因为这会使它变小。 我们只是增加了这个数字。

仅当后缀变为00...11才会消失。 最小的后缀由全零组成。 因此,当我们遇到11 ,我们可以立即将其清零并将其后的所有位清零。 然后我们将1添加到后缀左侧的位。

例如,考虑这个数字中最右边的11

        1000101011000100
                ^^
        suffix: 11000100
prefix: 10001010

我们将后缀清零,并在前缀后添加一个:

        suffix: 00000000
prefix: 10001011
result: 1000101100000000

现在我们继续向左搜索,寻找下一个11

以下函数将后缀从右移到零,在前缀上加一个,然后向左移以将前缀恢复到其位置。

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;
}

好的,我编译并测试了这个,所以我知道这有效。 首先,回答原始问题。 使用一些技巧可以在恒定的时间内快速完成此操作:

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模式,就可以将较低的零设置为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)

现在讨论迈克尔·拉斯洛提出的普遍问题。 只有在允许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;
}

这是一种替代方法,基于与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也是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)步骤。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM