2571. Minimum Operations to Reduce an Integer to 0
Problem Description
You are given a positive integer n
. You can perform the following operation any number of times:
- Add or subtract a power of 2 from
n
Your goal is to return the minimum number of operations needed to make n
equal to 0.
A number x
is a power of 2 if x = 2^i
where i >= 0
(for example: 1, 2, 4, 8, 16, ...).
The key insight is that when we look at the binary representation of n
, we need to eliminate all the 1-bits. The solution uses a greedy approach by processing the binary representation from right to left:
- When encountering isolated 1-bits (like
...010...
), we can eliminate them with a single subtraction operation - When encountering consecutive 1-bits (like
...0111...
), it's more efficient to add a power of 2 to convert them to...1000...
, which leaves us with just one 1-bit to handle later
The algorithm tracks consecutive 1-bits using a counter cnt
:
- If the current bit is 1, increment the counter
- If the current bit is 0 and we have accumulated 1-bits:
- If
cnt == 1
: one operation eliminates the single 1-bit - If
cnt > 1
: one operation (addition) converts multiple consecutive 1-bits into a single 1-bit at a higher position
- If
After processing all bits, any remaining consecutive 1-bits are handled:
- If
cnt == 1
: one more operation needed - If
cnt > 1
: two more operations needed (one to convert to a higher bit, one to eliminate it)
Intuition
To understand why this approach works, let's think about what happens when we add or subtract powers of 2 in binary representation.
When we subtract a power of 2 from n
, we're essentially flipping a single 1-bit to 0. For example, if n = 5 (101 in binary)
and we subtract 4 (100)
, we get 1 (001)
. This is straightforward for isolated 1-bits.
However, the challenge comes with consecutive 1-bits. Consider n = 7 (111 in binary)
. We could subtract powers of 2 three times: 7 - 4 = 3
, then 3 - 2 = 1
, then 1 - 1 = 0
. That's 3 operations.
But here's the key insight: instead of subtracting, we can add! If we add 1
to 7
, we get 8 (1000 in binary)
. Notice how adding 1
to 111
creates 1000
- all those consecutive 1-bits "collapse" into a single 1-bit at a higher position due to the carry propagation. Now we only need one more operation to subtract 8
, giving us a total of 2 operations instead of 3.
This pattern generalizes: whenever we see consecutive 1-bits in the binary representation, it's more efficient to add a power of 2 to create a carry that eliminates all but one of them, rather than subtracting each one individually.
The algorithm essentially simulates this process by scanning through the binary representation:
- Single 1-bits can be directly eliminated (1 operation)
- Multiple consecutive 1-bits should be "collapsed" into a single higher bit through addition (1 operation), which then needs to be eliminated later (another operation)
This greedy strategy of always choosing to add when facing consecutive 1-bits and subtract for isolated 1-bits gives us the minimum number of operations.
Learn more about Greedy and Dynamic Programming patterns.
Solution Approach
The solution implements a greedy algorithm that processes the binary representation of n
bit by bit from right to left using bitwise operations.
Algorithm Steps:
-
Initialize variables:
ans
: counts the total number of operations neededcnt
: tracks the current number of consecutive 1-bits encountered
-
Process each bit from right to left:
while n: if n & 1: # Current bit is 1 cnt += 1 elif cnt: # Current bit is 0 and we have accumulated 1-bits ans += 1 cnt = 0 if cnt == 1 else 1 n >>= 1 # Shift right to process next bit
- When we encounter a 1-bit (
n & 1
is true), we incrementcnt
to count consecutive 1s - When we encounter a 0-bit after some 1-bits:
- If
cnt == 1
: We had a single isolated 1-bit, which takes 1 operation to eliminate. Resetcnt
to 0 - If
cnt > 1
: We had consecutive 1-bits. One operation (addition) converts them to a single 1-bit at a higher position. Setcnt = 1
to account for this carried bit
- If
- When we encounter a 1-bit (
-
Handle remaining bits after the loop:
if cnt == 1: ans += 1 elif cnt > 1: ans += 2
- If
cnt == 1
: One more operation to eliminate the final 1-bit - If
cnt > 1
: Two operations needed - one to convert consecutive 1s to a higher bit, another to eliminate that bit
- If
Example Walkthrough:
For n = 39
(binary: 100111
):
- Process bits right to left:
1
,1
,1
,0
,0
,1
- First three 1s:
cnt
becomes 3 - First 0: Since
cnt = 3 > 1
, we add 1 operation and setcnt = 1
(carried bit) - Second 0: Since
cnt = 1
, we add 1 operation and setcnt = 0
- Final 1:
cnt
becomes 1 - After loop:
cnt = 1
, so add 1 more operation - Total: 3 operations
The algorithm efficiently handles the pattern where consecutive 1-bits should be "collapsed" through addition rather than eliminated one by one through subtraction.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through n = 14
(binary: 1110
) to illustrate the solution approach:
Initial state: n = 14
(binary: 1110
), ans = 0
, cnt = 0
Step 1: Process rightmost bit (0)
n & 1 = 0
, so current bit is 0cnt = 0
, so we skip the elif block- Shift right:
n = 7
(binary:111
)
Step 2: Process next bit (1)
n & 1 = 1
, so current bit is 1- Increment
cnt = 1
- Shift right:
n = 3
(binary:11
)
Step 3: Process next bit (1)
n & 1 = 1
, so current bit is 1- Increment
cnt = 2
- Shift right:
n = 1
(binary:1
)
Step 4: Process next bit (1)
n & 1 = 1
, so current bit is 1- Increment
cnt = 3
- Shift right:
n = 0
Step 5: Loop ends (n = 0)
- We have
cnt = 3
(three consecutive 1-bits) - Since
cnt > 1
, we need 2 operations:- First operation: Add 2 to convert
1110
to10000
(collapsing the three 1s into a single 1) - Second operation: Subtract 16 to reach 0
- First operation: Add 2 to convert
ans += 2
Final answer: 2 operations
This demonstrates how consecutive 1-bits (111
in positions 1-3) are more efficiently handled by adding a power of 2 to create a carry, rather than subtracting each bit individually (which would take 3 operations).
Solution Implementation
1class Solution:
2 def minOperations(self, n: int) -> int:
3 # Initialize result counter and consecutive 1-bits counter
4 operations = 0
5 consecutive_ones = 0
6
7 # Process each bit of n from right to left
8 while n > 0:
9 # Check if the current least significant bit is 1
10 if n & 1:
11 # Increment counter for consecutive 1-bits
12 consecutive_ones += 1
13 elif consecutive_ones > 0:
14 # We hit a 0 after seeing 1s, process the group of 1s
15 operations += 1
16 # Reset counter: if we had exactly one 1, start fresh
17 # Otherwise, carry over 1 (for cases like 11 -> 100)
18 consecutive_ones = 0 if consecutive_ones == 1 else 1
19
20 # Right shift to check the next bit
21 n >>= 1
22
23 # Handle any remaining consecutive 1-bits after the loop
24 if consecutive_ones == 1:
25 # Single 1-bit requires one operation
26 operations += 1
27 elif consecutive_ones > 1:
28 # Multiple consecutive 1-bits require two operations
29 operations += 2
30
31 return operations
32
1class Solution {
2 public int minOperations(int n) {
3 int operationCount = 0;
4 int consecutiveOnes = 0;
5
6 // Process each bit of n from right to left
7 while (n > 0) {
8 // Check if the current least significant bit is 1
9 if ((n & 1) == 1) {
10 // Increment count of consecutive 1-bits
11 consecutiveOnes++;
12 } else if (consecutiveOnes > 0) {
13 // We've encountered a 0 after a sequence of 1-bits
14 // Process the group of consecutive 1-bits
15 operationCount++;
16
17 // If we had exactly one 1-bit, we're done with this group
18 // If we had multiple 1-bits, we carry over 1 (like binary addition)
19 consecutiveOnes = (consecutiveOnes == 1) ? 0 : 1;
20 }
21
22 // Shift n right by 1 bit to process the next bit
23 n >>= 1;
24 }
25
26 // Handle any remaining consecutive 1-bits at the most significant positions
27 if (consecutiveOnes == 1) {
28 // Single remaining 1-bit requires one operation
29 operationCount += 1;
30 } else if (consecutiveOnes > 1) {
31 // Multiple remaining 1-bits require two operations
32 operationCount += 2;
33 }
34
35 return operationCount;
36 }
37}
38
1class Solution {
2public:
3 int minOperations(int n) {
4 int operations = 0; // Total number of operations needed
5 int consecutiveOnes = 0; // Count of consecutive 1-bits encountered
6
7 // Process each bit of n from right to left
8 while (n > 0) {
9 if ((n & 1) == 1) {
10 // Current bit is 1: increment consecutive ones counter
11 consecutiveOnes++;
12 } else if (consecutiveOnes > 0) {
13 // Current bit is 0 and we have accumulated consecutive 1s
14 // Time to process the group of consecutive 1s we just finished counting
15 operations++;
16
17 // If we had exactly one 1-bit, we can clear it with one operation
18 // If we had multiple consecutive 1s, we need to handle carry-over
19 consecutiveOnes = (consecutiveOnes == 1) ? 0 : 1;
20 }
21
22 n >>= 1; // Shift to examine the next bit
23 }
24
25 // Handle any remaining consecutive 1s after processing all bits
26 if (consecutiveOnes == 1) {
27 operations += 1; // Single 1-bit requires one operation
28 } else if (consecutiveOnes > 1) {
29 operations += 2; // Multiple consecutive 1s require two operations
30 }
31
32 return operations;
33 }
34};
35
1function minOperations(n: number): number {
2 let operationCount: number = 0;
3 let consecutiveOnes: number = 0;
4
5 // Process each bit of n from right to left
6 while (n > 0) {
7 // Check if the current least significant bit is 1
8 if (n & 1) {
9 // Count consecutive 1-bits
10 consecutiveOnes++;
11 } else if (consecutiveOnes > 0) {
12 // When we encounter a 0 after consecutive 1s, process them
13 operationCount++;
14
15 // If we had exactly one 1-bit, reset counter
16 // If we had multiple consecutive 1-bits, keep one (for carry effect)
17 consecutiveOnes = consecutiveOnes === 1 ? 0 : 1;
18 }
19
20 // Shift n right by 1 bit to process the next bit
21 n >>= 1;
22 }
23
24 // Handle remaining consecutive 1-bits after processing all bits
25 if (consecutiveOnes === 1) {
26 // Single remaining 1-bit requires one operation
27 operationCount++;
28 } else if (consecutiveOnes > 1) {
29 // Multiple consecutive 1-bits at the end require two operations
30 operationCount += 2;
31 }
32
33 return operationCount;
34}
35
Time and Space Complexity
The time complexity is O(log n)
, where n
is the given integer. This is because the while loop iterates through each bit of the binary representation of n
. In each iteration, we perform a right shift operation (n >>= 1
), which effectively divides n
by 2. The number of iterations required to reduce n
to 0 through repeated division by 2 is log₂(n)
, hence the logarithmic time complexity.
The space complexity is O(1)
as the algorithm only uses a constant amount of extra space. The variables ans
and cnt
are the only additional storage used, and their space requirements do not depend on the size of the input n
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Handle Remaining Bits After the Loop
The Pitfall:
A common mistake is only processing bits while iterating through n
and forgetting that there might be consecutive 1-bits remaining after the loop ends. This happens when the most significant bits are 1s.
Incorrect Implementation:
def minOperations(self, n: int) -> int:
operations = 0
consecutive_ones = 0
while n > 0:
if n & 1:
consecutive_ones += 1
elif consecutive_ones > 0:
operations += 1
consecutive_ones = 0 if consecutive_ones == 1 else 1
n >>= 1
# Missing: handling of remaining consecutive_ones!
return operations
Why It Fails:
For n = 4
(binary: 100
), the loop processes the bit and consecutive_ones
becomes 1, but we never add the operation needed to eliminate this final 1-bit. The function would return 0 instead of 1.
The Fix: Always check and handle any remaining consecutive 1-bits after the main loop:
if consecutive_ones == 1: operations += 1 elif consecutive_ones > 1: operations += 2
2. Incorrect Carry-Over Logic for Consecutive 1-bits
The Pitfall:
When encountering a 0-bit after multiple consecutive 1-bits, incorrectly resetting consecutive_ones
to 0 instead of 1. The key insight is that adding a power of 2 to consecutive 1-bits creates a carry that propagates to the next higher bit position.
Incorrect Implementation:
elif consecutive_ones > 0: operations += 1 consecutive_ones = 0 # Wrong! Should be 1 when consecutive_ones > 1
Why It Fails:
For n = 3
(binary: 11
), after processing the two 1-bits and encountering the implicit 0 at the end, we should recognize that adding 1 converts 11
to 100
, leaving us with one more 1-bit to handle. If we reset to 0, we miss accounting for this carried bit.
The Fix: Use conditional logic to properly handle the carry:
consecutive_ones = 0 if consecutive_ones == 1 else 1
3. Misunderstanding When to Increment Operations
The Pitfall: Incrementing the operation counter immediately when seeing 1-bits, rather than waiting to process a complete group of consecutive 1s.
Incorrect Implementation:
while n > 0: if n & 1: consecutive_ones += 1 operations += 1 # Wrong! Don't count operations here # ...
Why It Fails: This approach counts each 1-bit as an operation, missing the optimization that consecutive 1-bits can be handled more efficiently through addition rather than multiple subtractions.
The Fix:
Only increment operations
when you've identified a complete group of consecutive 1-bits (when transitioning from 1s to 0, or at the end of processing):
if n & 1: consecutive_ones += 1 # Just count, don't add operations yet elif consecutive_ones > 0: operations += 1 # Now we know the group size, add operation
Which of these pictures shows the visit order of a depth-first search?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!