Find the closest integer with same weight O(1)
Asked Answered
C

14

6

I am solving this problem:

The count of ones in binary representation of integer number is called the weight of that number. The following algorithm finds the closest integer with the same weight. For example, for 123 (0111 1011)₂, the closest integer number is 125 (0111 1101)₂.

The solution for O(n) where n is the width of the input number is by swapping the positions of the first pair of consecutive bits that differ.

Could someone give me some hints for solving in it in O(1) runtime and space ?

Thanks

Consociate answered 22/7, 2016 at 10:8 Comment(5)
Writing the number itself in decimal or binary representation will not be O(1).Sabellian
This comes from the book elements of programming interview in JavaConsociate
if number is 0 or contains all 1's (2^x == number + 1) in binary, then cant be any answer. but to check even this cant be done in O(1).Pseudohermaphrodite
what is the name of the book, i would like to learn some of this tooZandrazandt
@AbhishekBansal That depends on your model of computation. For example, in the common RAM model, you assume a word size w, but assume that arithmetic operations on words of that size are O(1). So for example, you can add two w-bit numbers in O(1), but looping over their bits is Omega(w)Wilfredowilfrid
C
22

As already commented by ajayv this cannot really be done in O(1) as the answer always depends on the number of bits the input has. However, if we interpret the O(1) to mean that we have as an input some primitive integer data and all the logic and arithmetic operations we perform on that integer are O(1) (no loops over the bits), the problem can be solved in constant time. Of course, if we changed from 32bit integer to 64bit integer the running time would increase as the arithmetic operations would take longer on hardware.

One possible solution is to use following functions. The first gives you a number where only the lowest set bit of x is set

int lowestBitSet(int x){
  ( x & ~(x-1) ) 
}

and the second the lowest bit not set

int lowestBitNotSet(int x){
  return ~x & (x+1);
}

If you work few examples of these on paper you see how they work.

Now you can find the bits you need to change using these two functions and then use the algorithm you already described.

A c++ implementation (not checking for cases where there are no answer)

unsigned int closestInt(unsigned int x){
  unsigned int ns=lowestBitNotSet(x);
  unsigned int s=lowestBitSet(x);
  if (ns>s){
    x|=ns;
    x^=ns>>1;
  }
  else{
    x^=s;
    x|=s>>1;
  }
  return x;
}
Cytologist answered 22/7, 2016 at 11:7 Comment(5)
Shouldn't it be left shift operator in the else condition instead of right shift operator. For instance - Try to find out the closest integer of 14. It should be 13 but the algorithm seems to return 15 that is incorrect.Profess
@Rahul Are you sure? I do get the correct answer. See: ideone.com/LYmlZKCytologist
you are right. I was calculating the first statement in else condition with ns instead of s :( .Profess
@AriHietanen Can we reduce the operation step to a single line like in this working Python code snippet if (lowest_set < lowest_not_set): x ^= lowest_not_set | (lowest_not_set >> 1) else: x ^= lowest_set | (lowest_set >> 1)Carr
@Kaustubh Yes, that is correct as well.Mersey
V
6

To solve this problem in O(1) time complexity it can be considered that there are two main cases:

1) When LSB is '0':

In this case, the first '1' must be shifted with one position to the right.

Input : "10001000" 

Out ::: "10000100" 

2) When LSB is '1':

In this case the first '0' must be set to '1', and first '1' must be set to '0'.

Input : "10000111" 

Out ::: "10001110" 

The next method in Java represents one solution.

private static void findClosestInteger(String word) { // ex: word = "10001000"

    System.out.println(word);           // Print initial binary format of the number
    int x = Integer.parseInt(word, 2);  // Convert String to int

    if((x & 1) == 0) {                  // Evaluates LSB value
        // Case when LSB = '0':
        // Input:  x = 10001000

        int firstOne = x & ~(x -1);     // get first '1' position (from right to left)
        // firstOne = 00001000
        x = x & (x - 1);                // set first '1' to '0'
        // x = 10000000
        x = x | (firstOne >> 1);        // "shift" first '1' with one position to right
        // x = 10000100

    } else {
        // Case when LSB = '1':
        // Input: x = 10000111

        int firstZero = ~x & ~(~x - 1); // get first '0' position (from right to left)
        // firstZero = 00001000
        x = x & (~1);                   // set first '1', which is the LSB, to '0'
        // x = 10000110
        x = x | firstZero;              // set first '0' to '1'
        // x = 10001110

    }

    for(int i = word.length() - 1; i > -1  ; i--) {       // print the closest integer with same weight
        System.out.print("" + ( ( (x & 1 << i) != 0) ? 1 : 0) );
    }
}
Vespertine answered 29/8, 2019 at 16:8 Comment(1)
Case 2 is wrong, if the input is 10000111 then 10001011 is closer than 10001110Keitel
A
3

The problem can be viewed as "which differing bits to swap in a bit representation of a number, so that the resultant number is closest to the original?"

So, if we we're to swap bits at indices k1 & k2, with k2 > k1, the difference between the numbers would be 2^k2 - 2^k1. Our goal is to minimize this difference. Assuming that the bit representation is not all 0s or all 1s, a simple observation yields that the difference would be least if we kept |k2 - k1| as minimum. The minimum value can be 1. So, if we're able to find two consecutive different bits, starting from the least significant bit (index = 0), our job is done.

The case where bits starting from Least Significant Bit to the right most set bit are all 1s

                                   k2
                                   |
                         7 6 5 4 3 2 1 0
                         ---------------
                      n: 1 1 1 0 1 0 1 1

        rightmostSetBit: 0 0 0 0 0 0 0 1

     rightmostNotSetBit: 0 0 0 0 0 1 0 0 rightmostNotSetBit > rightmostSetBit so,

             difference: 0 0 0 0 0 0 1 0 i.e. rightmostNotSetBit - (rightmostNotSetBit >> 1):  
                         ---------------
         n + difference: 1 1 1 0 1 1 0 1

The case where bits starting from Least Significant Bit to the right most set bit are all 0s

                                   k2
                                   |
                         7 6 5 4 3 2 1 0
                         ---------------
                      n: 1 1 1 0 1 1 0 0

        rightmostSetBit: 0 0 0 0 0 1 0 0

     rightmostNotSetBit: 0 0 0 0 0 0 0 1 rightmostSetBit > rightmostNotSetBit so,

             difference: 0 0 0 0 0 0 1 0 i.e. rightmostSetBit -(rightmostSetBit>> 1)
                         ---------------
         n - difference: 1 1 1 0 1 0 1 0

The edge case, of course the situation where we have all 0s or all 1s.

    public static long closestToWeight(long n){
        if(n <= 0 /* If all 0s */ || (n+1) == Integer.MIN_VALUE /* n is MAX_INT */)
           return -1;
       long neg = ~n;
       long rightmostSetBit = n&~(n-1);
       long rightmostNotSetBit = neg&~(neg-1);
       if(rightmostNotSetBit > rightmostSetBit){
           return (n + (rightmostNotSetBit - (rightmostNotSetBit >> 1)));
       }
       return (n - (rightmostSetBit - (rightmostSetBit >> 1)));
}
Alberto answered 5/5, 2018 at 23:13 Comment(0)
L
3

Attempted the problem in Python. Can be viewed as a translation of Ari's solution with the edge case handled:

def closest_int_same_bit_count(x):
    # if all bits of x are 0 or 1, there can't be an answer
    if x & sys.maxsize in {sys.maxsize, 0}:
        raise ValueError("All bits are 0 or 1")

    rightmost_set_bit = x & ~(x - 1)
    next_un_set_bit = ~x & (x + 1)

    if next_un_set_bit > rightmost_set_bit:
        # 0 shifted to the right e.g 0111 -> 1011
        x ^= next_un_set_bit | next_un_set_bit >> 1
    else:
        # 1 shifted to the right 1000 -> 0100
        x ^= rightmost_set_bit | rightmost_set_bit >> 1

    return x

Similarly jigsawmnc's solution is provided below:

def closest_int_same_bit_count(x):
    # if all bits of x are 0 or 1, there can't be an answer
    if x & sys.maxsize in {sys.maxsize, 0}:
        raise ValueError("All bits are 0 or 1")

    rightmost_set_bit = x & ~(x - 1)
    next_un_set_bit = ~x & (x + 1)

    if next_un_set_bit > rightmost_set_bit:
        # 0 shifted to the right e.g 0111 -> 1011
        x += next_un_set_bit - (next_un_set_bit >> 1)
    else:
        # 1 shifted to the right 1000 -> 0100
        x -= rightmost_set_bit - (rightmost_set_bit >> 1)

    return x
Lifeline answered 29/4, 2020 at 10:46 Comment(0)
H
1

Java Solution:

//Swap the two rightmost consecutive bits that are different
for (int i = 0; i < 64; i++) {
        if ((((x >> i) & 1) ^ ((x >> (i+1)) & 1)) == 1) {
            // then swap them or flip their bits
            int mask = (1 << i) | (1 << i + 1);
            x = x ^ mask;
            System.out.println("x = " + x);
            return;
        }
    }
Hoagy answered 10/3, 2018 at 18:32 Comment(1)
This solution doesn't satisfy the constraints in the question: Could someone give me some hints for solving it in O(1) runtime and spaceLifeline
S
1

One of the examples in the answers above is incorrect:

Input: "10000111"  
Out ::: "10001110" // This is not the closest. The closest number is 10001011

In fact, there is no point in separating the case of even and odd numbers. The main thing is to comply with 2 conditions: the replaced bits must be adjacent and must be as close to the right as possible. Example of a function in Python:

def closest_int_same_bit_count_my(x) :
    # x = 10000111
    if x == 0 or x & (x + 1) == 0:
        raise ValueError('All bits are 0 or 1')
    b = x ^ (x >> 1)  # Shift right by 1 and XOR to find first different pair. Got 11000100
    c = b & ~(b - 1)  # Isolate lowest different bit. Got 100
    d = c | (c << 1)  # Add leading 1 for following XOR. Got 1100
    r = x ^ d  # XOR to swap bits. Got 10001011
    return r
Sutherlan answered 22/10, 2023 at 22:3 Comment(0)
L
0
static void findClosestIntWithSameWeight(uint x)
{
    uint xWithfirstBitSettoZero = x & (x - 1);
    uint xWithOnlyfirstbitSet = x & ~(x - 1);
    uint xWithNextTofirstBitSet = xWithOnlyfirstbitSet >> 1;

    uint closestWeightNum = xWithfirstBitSettoZero | xWithNextTofirstBitSet;

    Console.WriteLine("Closet Weight for {0} is {1}", x, closestWeightNum);

}
Lumbard answered 8/8, 2017 at 6:41 Comment(1)
Hi, thanks for the answer! Could you edit it to explain how your code works?Discourteous
C
0

Code in python:

def closest_int_same_bit_count(x):
    if (x & 1) != ((x >> 1) & 1):
        return x ^ 0x3

    diff = x ^ (x >> 1)
    rbs = diff & ~(diff - 1)
    i = int(math.log(rbs, 2))

    return x ^ (1 << i | 1 << i + 1)
Cutty answered 15/3, 2019 at 5:55 Comment(1)
Could you please share the intuition/why and how the code works especially L1 - L4Lifeline
T
0

A great explanation of this problem can be found on question 4.4 in EPI.
(Elements of Programming Interviews)

Another place would be this link on geeksforgeeks.org if you don't own the book.
(Time complexity may be wrong on this link)

Two things you should keep in mind here is (Hint if you're trying to solve this for yourself):
You can use x & (x - 1) to clear the lowest set-bit (not to get confused with LSB - least significant bit)
You can use x & ~(x - 1) to get/extract the lowest set bit

If you know the O(n) solution you know that we need to find the index of the first bit that differs from LSB.
If you don't know what the LBS is:

0000 0000
        ^ // it's bit all the way to the right of a binary string.

Take the base two number 1011 1000 (184 in decimal)
The first bit that differs from LSB:

1011 1000
     ^ // this one

We'll record this as K1 = 0000 1000
Then we need to swap it with the very next bit to the right:

0000 1000
      ^ // this one

We'll record this as K2 = 0000 0100
Bitwise OR K1 and K2 together and you'll get a mask
mask = K1 | k2 // 0000 1000 | 0000 0100 -> 0000 1100
Bitwise XOR the mask with the original number and you'll have the correct output/swap
number ^ mask // 1011 1000 ^ 0000 1100 -> 1011 0100

Now before we pull everything together we have to consider that fact that the LSB could be 0001, and so could a bunch of bits after that 1000 1111. So we have to deal with the two cases of the first bit that differs from the LSB; it may be a 1 or 0.

First we have a conditional that test the LSB to be 1 or 0: x & 1
IF 1 return x XORed with the return of a helper function
This helper function has a second argument which its value depends on whether the condition is true or not. func(x, 0xFFFFFFFF) // if true // 0xFFFFFFFF 64 bit word with all bits set to 1
Otherwise we'll skip the if statement and return a similar expression but with a different value provided to the second argument.
return x XORed with func(x, 0x00000000) // 64 bit word with all bits set to 0. You could alternatively just pass 0 but I did this for consistency

Our helper function returns a mask that we are going to XOR with the original number to get our output. It takes two arguments, our original number and a mask, used in this expression:
(x ^ mask) & ~((x ^ mask) - 1)
which gives us a new number with the bit at index K1 always set to 1.
It then shifts that bit 1 to the right (i.e index K2) then ORs it with itself to create our final mask
0000 1000 >> 1 -> 0000 0100 | 0001 0000 -> 0000 1100

This all implemented in C++ looks like:

unsigned long long int closestIntSameBitCount(unsigned long long int n)
{
  if (n & 1)
    return n ^= getSwapMask(n, 0xFFFFFFFF);
  return n ^= getSwapMask(n, 0x00000000);
}

// Helper function
unsigned long long int getSwapMask(unsigned long long int n, unsigned long long int mask)
{
  unsigned long long int swapBitMask = (n ^ mask) & ~((n ^ mask) - 1);
  return swapBitMask | (swapBitMask >> 1);
}

Keep note of the expression (x ^ mask) & ~((x ^ mask) - 1) I'll now run through this code with my example 1011 1000:

// start of closestIntSameBitCount
if (0) // 1011 1000 & 1 -> 0000 0000
// start of getSwapMask
getSwapMask(1011 1000, 0x00000000)
swapBitMask = (x ^ mask) & ~1011 0111  // ((x ^ mask) - 1) = 1011 1000 ^ .... 0000 0000 -> 1011 1000 - 1 -> 1011 0111
swapBitMask = (x ^ mask) & 0100 1000 // ~1011 0111 -> 0100 1000
swapBitMask = 1011 1000 & 0100 1000 // (x ^ mask) = 1011 1000 ^ .... 0000 0000 -> 1011 1000
swapBitMask = 0000 1000 // 1011 1000 & 0100 1000 -> 0000 1000
return swapBitMask | 0000 0100 // (swapBitMask >> 1) = 0000 1000 >> 1 -> 0000 0100
return 0000 1100 // 0000 1000 | 0000 0100 -> 0000 11000
// end of getSwapMask
return 1011 0100 // 1011 1000 ^ 0000 11000 -> 1011 0100
// end of closestIntSameBitCount

Here is a full running example if you would like compile and run it your self:

#include <iostream>
#include <stdio.h>
#include <bitset>

unsigned long long int closestIntSameBitCount(unsigned long long int n);
unsigned long long int getSwapMask(unsigned long long int n, unsigned long long int mask);

int main()
{
  unsigned long long int number;
  printf("Pick a number: ");
  std::cin >> number;
  std::bitset<64> a(number);
  std::bitset<64> b(closestIntSameBitCount(number));
  std::cout << a
            << "\n"
            << b
            << std::endl;
}

unsigned long long int closestIntSameBitCount(unsigned long long int n)
{
  if (n & 1)
    return n ^= getSwapMask(n, 0xFFFFFFFF);
  return n ^= getSwapMask(n, 0x00000000);
}

// Helper function
unsigned long long int getSwapMask(unsigned long long int n, unsigned long long int mask)
{
  unsigned long long int swapBitMask = (n ^ mask) & ~((n ^ mask) - 1);
  return swapBitMask | (swapBitMask >> 1);
}
Teniafuge answered 26/3, 2020 at 4:24 Comment(0)
H
0

This was my solution to the problem. I guess @jigsawmnc explains pretty well why we need to have |k2 -k1| to a minimum. So in order to find the closest integer, with the same weight, we would want to find the location where consecutive bits are flipped and then flip them again to get the answer. In order to do that we can shift the number 1 unit. Take the XOR with the same number. This will set bits at all locations where there is a flip. Find the least significant bit for the XOR. This will give you the smallest location to flip. Create a mask for the location and next bit. Take an XOR and that should be the answer. This won't work, if the digits are all 0 or all 1 Here is the code for it.

    def variant_closest_int(x: int) -> int:
        if x == 0 or ~x == 0:
            raise ValueError('All bits are 0 or 1')
        x_ = x >> 1
        lsb = x ^ x_
        mask_ = lsb & ~(lsb - 1)
        mask = mask_ | (mask_ << 1)
        return x ^ mask
Ham answered 3/7, 2020 at 0:9 Comment(0)
A
0

My solution, takes advantage of the parity of the integer. I think the way I got the LSB masks can be simplified

def next_weighted_int(x):
    
    if x % 2 == 0:
        lsb_mask = ( ((x - 1) ^ x) >> 1 ) + 1 # Gets a mask for the first 1
        x ^= lsb_mask
        x |= (lsb_mask >> 1)
        
        return x
    
    lsb_mask = ((x ^ (x + 1)) >> 1 ) + 1 # Gets a mask for the first 0
    x |= lsb_mask
    x ^= (lsb_mask >> 1)

    return x
Aliment answered 25/11, 2020 at 14:33 Comment(0)
O
0

Just sharing my python solution for this problem:

def same closest_int_same_bit_count(a):
    x = a + (a & 1)  # change last bit to 0
    bit = (x & ~(x-1)) # get last set bit
    return a ^ (bit | bit >> 1) # swap set bit with unset bit
Omegaomelet answered 10/12, 2020 at 18:28 Comment(0)
P
0
func findClosestIntegerWithTheSameWeight2(x int) int {
        rightMost0 := ^x & (x + 1)
        rightMost1 := x & (-x)
        if rightMost0 > 1 {
            return (x ^ rightMost0) ^ (rightMost0 >> 1)
        } else {
            return (x ^ rightMost1) ^ (rightMost1 >> 1)
        }
    }
Pyrostat answered 16/8, 2021 at 7:42 Comment(0)
V
0
int BIT_MASK = (n & 1) == 0 ? (n & ~(n - 1)) : (~n & ~(~n - 1));
n ^= BIT_MASK;
n ^= (BIT_MASK >> 1);
Vivisectionist answered 22/3, 2023 at 23:18 Comment(1)
Please don't post only code as an answer, but including an explanation of what your code does and how it solves the problem of the question really helps to improve the quality of your post. Answers with an explanation are generally of higher quality, and are more likely to attract upvotes.Following

© 2022 - 2024 — McMap. All rights reserved.