![]() |
VOOZH | about |
Given a positive integer n, print the next smallest and the previous largest number that has the same number of 1 bit in their binary representation.
Examples:
Input : n = 5 Output : Closest Greater = 6 Closest Smaller = 3 Note that 5, 6 and 3 have same number of set bits. Input : n = 11 Output : Closest Greater = 13 Closest Smaller = 7
The Brute Force Approach
An easy approach is simple brute force: count the number of 1s in n, and then increment (or decrement) until we find a number with the same number of 1s.
Optimal Approaches
Let's start with the code for getNext, and then move on to getPrev.
Bit Manipulation Approach for Get Next Number
If we think about what the next number should be, we can observe the following. Given the number 13948, the binary representation looks like this:
1 1 0 1 1 0 0 1 1 1 1 1 0 0 13 12 11 10 9 8 7 6 5 4 3 2 1 0
We want to make this number bigger (but not too big). We also need to keep the same number of ones.
Observation: Given a number n and two-bit locations i and j, suppose we flip bit i from a 1 to a 0, and bit j from a 0 to a 1. If i > j, then n will have decreased. If i < j, then n will have increased.
We know the following:
To put this in a different way, we are flipping the rightmost non-trailing zero. That is, using the above example, the trailing zeros are in the 0th and 1st spot. The rightmost non-trailing zero is at 7. Let's call this position p.
p ==> Position of rightmost non-trailing 0.
Step 1: Flip rightmost non-trailing zero
1 1 0 1 1 0 1 1 1 1 1 1 0 0 13 12 11 10 9 8 7 6 5 4 3 2 1 0
With this change, we have increased the number of 1s of n. We can shrink the number by rearranging all the bits to the right of bit p such that the 0s are on the left and the 1s are on the right. As we do this, we want to replace one of the 1s with a 0.
A relatively easy way of doing this is to count how many ones are to the right of p, clear all the bits from 0 until p, and then add them back to c1-1 ones. Let c1 be the number of ones to the right of p and c0 be the number of zeros to the right of p.
Let's walk through this with an example.
c1 ==> Number of ones to the right of p c0 ==> Number of zeros to the right of p. p = c0 + c1
Step 2: Clear bits to the right of p. From before, c0 = 2. c1 = 5. p = 7.
1 1 0 1 1 0 1 0 0 0 0 0 0 0 13 12 11 10 9 8 7 6 5 4 3 2 1 0
To clear these bits, we need to create a mask that is a sequence of ones, followed by p zeros. We can do this as follows:
// all zeros except for a 1 at position p. a = 1 << p; // all zeros, followed by p ones. b = a - 1; // all ones, followed by p zeros. mask = ~b; // clears rightmost p bits. n = n & mask; Or, more concisely, we do: n &= ~((1 << p) - 1).
Step 3: Add one c1 - 1 one.
1 1 0 1 1 0 1 0 0 0 1 1 1 1 13 12 11 10 9 8 7 6 5 4 3 2 1 0
To insert c1 - 1 one on the right, we do the following:
// 0s with a 1 at position c1– 1 a = 1 << (c1 - 1); // 0s with 1s at positions 0 through c1-1 b = a - 1; // inserts 1s at positions 0 through c1-1 n = n | b; Or, more concisely: n | = (1 << (c1 - 1)) - 1;
We have now arrived at the smallest number, bigger than n with the same number of ones. The implementation of the code for getNext is below.
Output:
6 16
The time complexity of the above code is O(log n) as we are looping through the bits of the given integer n.
The space complexity of the above code is O(1) as no extra space is required.
Optimal Bit Manipulation Approach for Get Previous Number
To implement getPrev, we follow a very similar approach.
Note that Step 2 sets bits p to zero and Step 3 sets bits 0 through p-1 to zero. We can merge these steps.
Let's walk through this with an example.
c1 ==> number of trailing ones c0 ==> size of the block of zeros immediately to the left of the trailing ones. p = c1 + c0
Step 1: Initial Number: p = 7. c1 = 2. c0 = 5.
1 0 0 1 1 1 1 0 0 0 0 0 1 1 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Steps 2 & 3: Clear bits 0 through p.
1 0 0 1 1 1 0 0 0 0 0 0 0 0 13 12 11 10 9 8 7 6 5 4 3 2 1 0
We can do this as follows:
// Sequence of 1s int a = ~0; // Sequence of 1s followed by p + 1 zeros. int b = a << (p + 1); // Clears bits 0 through p. n & = b;
Step 4: Insert c1 + 1 ones immediately to the right of position p.
1 0 0 1 1 1 0 1 1 1 0 0 0 0 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Note that since p =c1 + c0, then (c1 + 1) ones will be followed by (c0 – 1)zeros.
We can do this as follows:
// 0s with 1 at position (c1 + 1) int a = 1 << (c1 + 1); // 0s followed by c1 + 1 ones int b = a - 1; // c1 + 1 ones followed by c0 - 1 zeros. int c = b << (c0 - 1); n |= c;
The code to implement this is below.
Output:
5 8
Time Complexity: O(logn)
The time complexity of the above algorithm is O(logn) as we are iterating through the bits of a number while computing c0 and c1.
Space Complexity: O(1)
The algorithm runs in constant space O(1) as no extra space is used.
Arithmetic Approach to Get Next Number
If c0 is the number of trailing zeros, c1 is the size of the one block immediately following, and p = c0 + c1, we can form our solution from earlier as follows:
A quick way to perform steps 1 and 2 is to set the trailing zeros to 1 (giving us p trailing ones), and then add 1. Adding one will flip all trailing ones, so we wind up with a 1 at bit p followed by p zeros. We can do this arithmetically.
// Sets trailing 0s to 1, giving us p trailing 1s.
n += 2c0 - 1 ;
// Flips first p ls to 0s and puts a 1 at bit p.
n += 1;
Now, to perform Step 3 arithmetically, we just do:
// Sets trailing c1 - 1 zeros to ones.
n += 2c1 - 1 - 1;
This math reduces to:
next = n + (2c0 - 1) + 1 + (2c1 - 1 - 1)
= n + 2c0 + 2c1 - 1 – 1
The best part is that using a little bit of manipulation, it's simple to code.
Output :
6 16
Time Complexity: O(logn).
Space Complexity: O(1)
Arithmetic Approach to Get Previous Number
If c1 is the number of trailing ones, c0 is the size of the zero block immediately following, and p =c0 + c1, we can word the initial getPrev solution as follows:
We can implement this arithmetically as follows. For clarity in the example, we assume n = 10000011. This makes c1 = 2 and c0 = 5.
// Removes trailing 1s. n is now 10000000. n -= 2c1 – 1; // Flips trailing 0s. n is now 01111111. n -= 1; // Flips last (c0-1) 0s. n is now 01110000. n -= 2c0 - 1 - 1; This reduces mathematically to: next = n - (2c1 - 1) - 1 - ( 2c0-1 - 1) . = n - 2c1 - 2c0-1 + 1;
Again, this is very easy to implement.
Output :
5 8
Time complexity: O(log n) where n is the value of the input integer n.
Space complexity: O(1) as the algorithm uses only a constant amount of additional memory.
This article is contributed by Mr. Somesh Awasthi.