【问题标题】:Get next non consecutive ones binary number获取下一个非连续二进制数
【发布时间】:2014-11-27 20:08:47
【问题描述】:

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

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

除了转到下一个数字并查看连续位之外,谁能告诉我方法。

【问题讨论】:

  • 这个家庭作业什么时候交?
  • 这不是我想在编码网站上练习的作业
  • 蛮力,转到下一个数字,遍历该数字以找到连续的 1,但这将花费大量时间遍历每个下一个数字,直到我找到连续的。
  • 提示:x & (x >> 1) 是什么?

标签: c++ c algorithm data-structures


【解决方案1】:

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

考虑11 两侧的位:

...11...

我们会说11... 是当前号码的后缀。想象一下,我们一直在后缀上加 1,直到 11 消失。那什么时候会发生?好吧,11 不会变成1001,因为这会使它变小。我们只是在增加数量。

11 只会在后缀变为00... 时消失。最小的此类后缀由全零组成。因此,当我们遇到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;
}

【讨论】:

  • 当我只能抱怨使用// 而不是/* */ 时,干得好:)
  • 哈哈,谢谢。在编写 C++ 时,我更喜欢 //。这是一个 ANSI C 程序,因此是 /* */
  • 你为什么要扫描整个东西?
  • 您可以在恒定时间内检查11 的存在。但是如果有11,在最坏的情况下你必须扫描所有的位。由于成本如此之小,我们不妨扫描所有位。渐近成本不大于读取输入的成本。
  • 如果输入是n位的序列,则读取输入需要O(n)时间,上面的算法也需要O(n)时间。如果输入由恒定数量的位组成,则上述算法也需要恒定的时间。
【解决方案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 模式后,您可以将较低的零设置为 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) 时间来隔离数字的高位。基本思路和之前一样。唯一的区别是,我们必须找到高于最高 11 模式的最低 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;
}

【讨论】:

  • 这是不正确的。用 4020 试试。你返回:4020 -&gt; 4021,即0111110110100 -&gt; 0111110110101
  • 原来的问题表明给定的数字已经符合模式,而 4020 不符合。
  • 你是对的。我想我正在考虑一个更普遍的问题。你能找到一个可能包含连续输入的恒定时间解决方案吗?
【解决方案3】:

这是另一种方法,其基本原理与 CLZ 方法大致相同,但由于它是从右到左而不是从左到右工作的,因此它可能需要几个步骤。与发布的其他从右到左的方法不同,它只会访问有问题的站点,而不是遍历所有位。

cmets 中提到的公式x &amp; (x &gt;&gt; 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(我更愿意称之为-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 几乎总是导致“后缀或”,这次确实如此。

重点是隔离最高的1,然后减去1,可以像这样直接完成(即没有clz和shift):(这里的循环称为“suffix-OR”)

cbl1 >>= 1;
for (int i = 1; i < sizeof(something) * CHAR_BIT; i <<= 1)
    dbl1 |= dbl1 >> i;

当然不是一个真正的循环,不展开它会很愚蠢(实际上它基本上是 5 或 6 次迭代)。

你可以把它算作常数时间,取决于你喜欢什么——如果你想考虑你正在使用变量的类型的大小,那么这件事需要 O(log n) 个步骤。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-01
    • 1970-01-01
    • 2016-02-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多