简体   繁体   中英

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.

Next is 6 (110), 7 (111), 8 ( 1000). so my answer should be 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. 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...

We'll say that 11... is the suffix of the current number. Imagine that we keep adding 1 to the suffix until the 11 goes away. When is that going to happen? Well, the 11 isn't going to turn into 10 or 01 , because that would make it smaller. We are only increasing the number.

The 11 is only going to disappear when the suffix becomes 00... . 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. Then we add 1 to the bits that are to the left of the suffix.

For example, consider the rightmost 11 in this number:

        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 .

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.

#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. 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). 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. 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. Otherwise, it takes log(n) time to isolate the high bit of a number. 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. In other words, we must find the highest 11 pattern first and then search bits higher than that for a 00 pattern.

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. 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. 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)

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.

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). In short, if 11 does not occur, the number is unchanged.

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.


And here's a way to get rid of the CLZ in the beautiful CLZ method by JS1. Rewriting CLZ almost always results in a "suffix-OR", and indeed it does this time.

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")

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).

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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