简体   繁体   English

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

[英]Get next non consecutive ones binary number

I have been doing a question in which i have been given a number lets say 5 ( 101) not having any consecutive one, i need to find the next number which have non consecuitve ones. 我一直在做一个问题,其中我给了一个数字,让我们说5(101)没有任何连续的,我需要找到下一个没有连续的数字。

Next is 6 (110), 7 (111), 8 ( 1000). 接下来是6(110),7(111),8(1000)。 so my answer should be 8. 所以我的回答应该是8。

Can anyone tell me the approach , other than going to next number and see consecutive bits. 任何人都可以告诉我这种方法,除了转到下一个数字并看到连续的位。

A fast way to solve this problem is to scan the bits of the number until you find two consecutive 1 s. 解决此问题的一种快速方法是扫描数字的位,直到找到两个连续的1秒。 When this happens, you want to fix it. 发生这种情况时,您需要修复它。 In other words, you want to make a number that is slightly greater than the current number. 换句话说,您想要使数字略大于当前数字。 How much greater, exactly? 究竟要大多少?

Consider the bits on either side of the 11 : 考虑一下11两侧的位:

...11...

We'll say that 11... is the suffix of the current number. 我们说11...是当前数字的后缀 Imagine that we keep adding 1 to the suffix until the 11 goes away. 想象一下,我们继续在后缀中添加1,直到11消失为止。 When is that going to happen? 什么时候会发生? Well, the 11 isn't going to turn into 10 or 01 , because that would make it smaller. 那么, 11不会变成1001 ,因为这会使它变小。 We are only increasing the number. 我们只是增加了这个数字。

The 11 is only going to disappear when the suffix becomes 00... . 仅当后缀变为00...11才会消失。 The smallest such suffix consists of all zeros. 最小的后缀由全零组成。 Thus, when we encounter a 11 , we can immediately zero it out and all bits that come after it. 因此,当我们遇到11 ,我们可以立即将其清零并将其后的所有位清零。 Then we add 1 to the bits that are to the left of the suffix. 然后我们将1添加到后缀左侧的位。

For example, consider the rightmost 11 in this number: 例如,考虑这个数字中最右边的11

        1000101011000100
                ^^
        suffix: 11000100
prefix: 10001010

We zero out the suffix and add one to the prefix: 我们将后缀清零,并在前缀后添加一个:

        suffix: 00000000
prefix: 10001011
result: 1000101100000000

Now we continue our search toward the left, looking for the next 11 . 现在我们继续向左搜索,寻找下一个11

The following function shifts right to zero out the suffix, adds one to the prefix, and shifts left to restore the prefix to its position. 以下函数将后缀从右移到零,在前缀上加一个,然后向左移以将前缀恢复到其位置。

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

This approach performs a constant number of operations for each bit of the input, making it quite a bit faster than the brute-force approach. 这种方法对输入的每一位执行恒定数量的操作,这使其比蛮力方法要快很多。 Formally, the running time is logarithmic in the size of the input. 形式上,运行时间是输入大小的对数。

Below is a complete program that takes the value of x on the command line. 下面是一个完整的程序,该程序在命令行上使用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;
}

Ok I compiled and tested this, so I know this works. 好的,我编译并测试了这个,所以我知道这有效。 First, the answer to the original problem. 首先,回答原始问题。 It can be done quickly in constant time using some bit tricks: 使用一些技巧可以在恒定的时间内快速完成此操作:

int getNext(int x)
{
    int invx = ~x;
    int dbl0 = invx & (invx >> 1);

    dbl0 =  (dbl0 & -dbl0);
    x   |=   dbl0;
    x   &= ~(dbl0 - 1);
    return x;
}

The idea behind this is that you need to find the rightmost 00 pattern in the number. 这背后的想法是你需要在数字中找到最右边的00模式。 Everything to the right of the 00 pattern must be of the form 10101010... because we know there are no other 00 patterns and no other 11 patterns to the right (the given number does not have any consecutive 1s). 00模式右边的所有内容都必须采用10101010的形式...,因为我们知道右边没有其他00模式,也没有其他11个模式(给定的数字没有连续的1)。 Once you find the rightmost 00 pattern, you can set the lower zero to one and clear all the other bits below that to zero. 一旦找到最右边的00模式,就可以将较低的零设置为1,并将其下方的所有其他位清除为零。 Example: 例:

1001010001010010101010
             ^  = lowest 00 pattern

1001010001010100000000
             ^ set this bit and clear all bits to its right

The bit trick steps are as follows: 位技巧步骤如下:

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)

Now on to the general problem posed by Michael Laszlo. 现在讨论迈克尔·拉斯洛提出的普遍问题。 This can be solved in constant time only if a CLZ (count leading zero) builtin is allowed. 只有在允许CLZ(计数前导零)内置时,才能在恒定时间内解决这个问题。 Otherwise, it takes log(n) time to isolate the high bit of a number. 否则,需要log(n)时间来隔离数字的高位。 The basic idea is the same as before. 基本思路与以前一样。 The only difference is that instead of finding the lowest 00 pattern, we must find the lowest 00 pattern that is higher than the highest 11 pattern. 唯一的区别是,我们必须找到最高的00模式,而不是找到最低的00模式。 In other words, we must find the highest 11 pattern first and then search bits higher than that for a 00 pattern. 换句话说,我们必须首先找到最高的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;
}

Here's an alternative method, based on roughly the same idea as the CLZ method, except since it works from right to left instead of left to right it can take several steps. 这是一种替代方法,基于与CLZ方法大致相同的思想,但由于它是从右到左而不是从左到右工作,因此可以采取几个步骤。 Unlike the other right to left method posted, it will visit only the problem sites, instead of iterating over all bits. 与发布的其他从右到左方法不同,它只访问问题站点,而不是遍历所有位。

The formula x & (x >> 1) mentioned in the comments makes a mask of all the bits that are 1 and have a 1 immediately to the left of them. 注释中提到的公式x & (x >> 1)对所有为1的位进行掩码,并在它们的左边立即有1。 So the lowest 1 bit in that mask is the "first problem", which we can solve as follows: (choose uint to be some kind of unsigned type) 因此,该掩码中的最低1位是“第一个问题”,我们可以按以下步骤解决:(选择uint为某种无符号类型)

uint m = x & (x >> 1);
uint lowest = m & -m;    // get first problem
uint newx = (x + lowest) & (lowest - 1); // solve it

The last line increments the 11 found and zeroes out everything to the right of it. 最后一行增加找到的11,并将其右边的所有内容清零。

If there is no problem (that is, m = 0 ), then lowest = 0 also, so in the last line, nothing is added and you AND with all-ones (I would prefer to call that -1, but this is a C question so someone would complain about that). 如果没有问题(也就是m = 0 ),那么lowest = 0也是lowest = 0 ,所以在最后一行,没有添加任何东西,你和所有的一起(我宁愿称之为-1,但这是一个C问题所以有人会抱怨这个)。 In short, if 11 does not occur, the number is unchanged. 简而言之,如果没有发生11,则数量不变。

That would have been great in case we could have used this without any control flow, but we can't - fixing one problem may cause one further to the left (or it may already have been there). 如果我们可以在没有任何控制流程的情况下使用它,这本来是很好的,但我们不能 - 修复一个问题可能会导致一个问题进一步向左(或者它可能已经存在)。 So this has to be looped until there are no more problems, for example like: (not tested) 所以这必须循环,直到没有更多的问题,例如:(未测试)

uint m = x & (x >> 1);
do {
    uint lowest = m & -m;
    x = (x + lowest) & (lowest - 1);
    m = x & (x >> 1);
} while (m);

Obviously it expects the original number plus 1 as input, otherwise it can return the same number. 显然它希望原始数字加1作为输入,否则它可以返回相同的数字。


And here's a way to get rid of the CLZ in the beautiful CLZ method by JS1. 这是JS1在漂亮的CLZ方法中摆脱CLZ的一种方法。 Rewriting CLZ almost always results in a "suffix-OR", and indeed it does this time. 重写CLZ几乎总是会产生“后缀-OR”,这次确实会这样做。

The point was to isolate the highest 1 and then subtract one, that can be done directly (ie without clz and shift) like this: (the loop here is called "suffix-OR") 关键是要隔离最高1然后减去1,这可以直接完成(即没有clz和shift),如下所示:(这里的循环称为“后缀-OR”)

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

Not a real loop of course, it would be silly not to unroll it (basically it's going to be either 5 or 6 iterations in practice). 当然,这不是一个真正的循环,不展开它会很愚蠢(实际上,实际上是5或6次迭代)。

You could count it as constant time or not, depending on what takes your fancy - if you want to consider the size of the type you're working with a variable, then this thing takes O(log n) steps. 你可以把它算作恒定时间,取决于你想要的东西 - 如果你想考虑你正在使用变量的类型的大小,那么这个东西需要O(log n)步骤。

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

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