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