2571. Minimum Operations to Reduce an Integer to 0
Problem Description
You are given a positive integer n
. The task is to transform n
to 0
by repeatedly performing the following operation: add or subtract a power of 2
to/from n
. The available powers of 2
range from 2^0
up to any higher power (as 2^1
, 2^2
, and so on). The goal is to find the minimum number of such operations required to achieve n = 0
.
In simpler words, you're essentially allowed to repeatedly increase or decrease the integer n
by any value that is a power of 2
(like 1
, 2
, 4
, 8
, and so on), and you need to find the least number of times you have to do this to get to 0
.
Intuition
The solution involves a strategic approach rather than trying out different powers of 2
at random. We look at the binary representation of the given integer n
. Since binary representation is based on powers of 2
, incrementing or decrementing by powers of 2
can be visualized as flipping specific bits in the binary form.
So, we scan through the binary digits (bits) of n
from least significant to most significant (from the right-hand side to the left).
-
If we encounter a
1
bit, this represents a power of2
that we need to cancel out. We keep a running total of consecutive1
bits because a string of1
s may be dealt with more efficiently together rather than individually. -
When we encounter a
0
bit, this provides an opportunity to address the consecutive1
bits encountered before it. If there's only one1
bit, we can perform a single subtract operation to remove it. If there are multiple1
s, we can turn all but one of them into0
by adding a power of2
that's just higher than the string of1
s. This adds1
to the count of subtract operations needed.
In the end, we might be left with a string of 1
s at the most significant end. We'll need two operations if there are multiple 1
s (add and then subtract the next higher power of 2
), or just one if there's a single 1
.
The algorithm hence devised is cautious; it chooses the most effective operation at each step due to its bitwise considerations, ensuring fewer total operations.
Learn more about Greedy and Dynamic Programming patterns.
Solution Approach
The Python code provided implements the algorithm through a mix of bitwise operations and a greedy approach.
Let's walk through the implementation:
-
Initialize
ans
andcnt
. The variableans
keeps track of the total number of operations required, andcnt
counts the consecutive1
bits in the binary representation ofn
. -
Process the binary digits of
n
from least significant to most significant. This is done by examiningn
bit by bit in a loop where the iteration involves a right shift operationn >>= 1
, effectively moving to the next bit towards the left. -
When the current bit is
1
(checked byif n & 1
), increment thecnt
by1
. This is because each1
represents a power of2
that must be addressed. -
If the current bit is
0
andcnt
is not zero, this indicates we have just finished processing a sequence of1
bits. Now, determine how to handle them:- If
cnt
equals1
, incrementans
by1
, as we need one operation to subtract this power of2
. - If
cnt
is more than1
, incrementans
by1
and setcnt
to1
. The rationale here is that we've added a power of2
that turns all but one of the consecutive1
bits into0
. We're left with one last subtraction operation to perform later.
- If
-
After processing all bits, there may be a leftover
cnt
:- If
cnt
is1
, we only need one more operation, so add1
toans
. - If
cnt
is greater than1
, we need a two-step operation where we first add and then subtract the next power of2
, hence add2
toans
.
- If
-
Finally, return the accumulated result
ans
, which gives us the minimum number of operations needed to transformn
into0
.
By leveraging bitwise operations, the solution is both efficient and straightforward. There's no need for iteration or recursion that directly considers powers of 2
; instead, the solution operates implicitly on powers of 2
by manipulating binary digits, which are intrinsically linked to powers of 2
.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution approach using the example of n = 23
. The binary representation of 23
is 10111
. We will apply the solution approach step by step:
- Initialize
ans = 0
andcnt = 0
. No operations have been done yet. - As
n
is10111
, we process from the rightmost bit to the left:- Bit 1 (rightmost):
n & 1
is true so incrementcnt
to 1. - Bit 2:
n & 1
is true so incrementcnt
to 2. - Bit 3:
n & 1
is true so incrementcnt
to 3. - Bit 4:
n & 1
is false, andcnt
is 3. Therefore, incrementans
to 1 and resetcnt
to 1 (because we perform an add operation to turn the sequence111
into1000
, which leaves one operation for later). - Last shift will happen, and now
n
becomes2
(10
in binary), and we've processed all bits.
- Bit 1 (rightmost):
- Leftover
cnt
is 1 because our binary representation now starts with1
. We incrementans
by 1 because a final subtract operation is needed. - Finally,
ans
is2
because we performed 2 operations in total: converting10111
to11000
(+1 operation) and then to0
(-1 operation).
The minimum number of operations required to transform n = 23
into 0
using the solution approach is 2
.
Solution Implementation
1class Solution:
2 def minOperations(self, n: int) -> int:
3 # Initialize operations count (ops) and a counter for consecutive ones (consecutive_ones).
4 ops = 0
5 consecutive_ones = 0
6
7 # Process all bits of the integer n from right to left (LSB to MSB).
8 while n:
9 # Check if the least significant bit (LSB) is 1.
10 if n & 1:
11 # If it is, increase the counter for consecutive ones.
12 consecutive_ones += 1
13 # If it is 0 and the counter for consecutive ones is not zero.
14 elif consecutive_ones:
15 # A zero after some ones means a completed set of "10" or "110."
16 # Operation needed to convert this set to "00" or "100".
17 ops += 1
18 # Reset the counter for consecutive ones.
19 # '1' in binary is already minimized, and for '11' we only need one operation
20 # to change it to '10' and then we 'carry the 1' so to speak.
21 consecutive_ones = 0 if consecutive_ones == 1 else 1
22
23 # Right shift n by 1 to check the next bit.
24 n >>= 1
25
26 # If there is a residual 1 then an operation is needed;
27 # this could be a trailing '1' or '10' after the end of the loop.
28 if consecutive_ones == 1:
29 # Increment the operations count by 1.
30 ops += 1
31 # If there are more than 1 ones, an extra operation is needed to handle the carry.
32 elif consecutive_ones > 1:
33 # Increment the operations count by 2. This is the case for '11' in binary,
34 # where one operation is to change '11' to '10' and second operation to change
35 # '10' to '00'.
36 ops += 2
37
38 # Return the number of operations required.
39 return ops
40
1class Solution {
2
3 public int minOperations(int num) {
4 // Initialize variables for answer and contiguous ones count
5 int operationsCount = 0;
6 int contiguousOnesCount = 0;
7
8 // Loop while number is greater than zero
9 for (; num > 0; num >>= 1) {
10 // If the least significant bit is 1
11 if ((num & 1) == 1) {
12 // Increment count of contiguous ones
13 contiguousOnesCount++;
14 } else if (contiguousOnesCount > 0) {
15 // If the least significant bit is 0 and we have seen a contiguous sequence of ones
16 operationsCount++; // Increase operations, since this is an operation to flip a zero after a one
17 // Reset/modify contiguous ones count based on the previous count
18 contiguousOnesCount = (contiguousOnesCount == 1) ? 0 : 1;
19 }
20 }
21
22 // After processing all bits, if there is a single one left, it is a valid operation to flip it
23 operationsCount += (contiguousOnesCount == 1) ? 1 : 0;
24
25 // If there are more than one contiguous ones left, we need two operations:
26 // one to flip the first one, and another one for the last one.
27 operationsCount += (contiguousOnesCount > 1) ? 2 : 0;
28
29 // Return the total number of operations required
30 return operationsCount;
31 }
32}
33
1class Solution {
2public:
3 int minOperations(int n) {
4 int operations = 0; // This will hold the total number of operations required.
5 int onesCount = 0; // This will count the number of consecutive '1' bits.
6
7 // Process each bit, starting with the least significant bit (LSB).
8 while (n > 0) {
9 if ((n & 1) == 1) {
10 // If the current bit is a '1', increment the count of consecutive ones.
11 ++onesCount;
12 } else if (onesCount > 0) {
13 // If we hit a '0' after counting some ones, we need an operation (either a flip or a move).
14 ++operations;
15
16 // If there was only one '1', we reset the count. Otherwise, we keep the count as '1',
17 // because the bit flip would transform "01" to "10", leaving us with one '1' to move.
18 onesCount = (onesCount == 1) ? 0 : 1;
19 }
20 // Right shift the bits in n to process the next bit in the next iteration.
21 n >>= 1;
22 }
23
24 // After processing all bits, if we have one '1' left, we need one more operation to remove it.
25 if (onesCount == 1) {
26 ++operations;
27 }
28
29 // If we have more than one '1', we need an additional two operations: one for flipping and one for moving.
30 if (onesCount > 1) {
31 operations += 2;
32 }
33
34 return operations; // Return the total number of operations calculated.
35 }
36};
37
1function minOperations(n: number): number {
2 let operationsCount = 0; // Will hold the total number of operations required.
3 let consecOnesCount = 0; // Counter for consecutive 1's in binary representation of n.
4
5 // Iterate through the bits of n.
6 while (n !== 0) {
7 // If the least significant bit is a 1.
8 if (n & 1) {
9 consecOnesCount += 1; // We found a consecutive one, increase the counter.
10 }
11 // If it's a 0 and there are consecutive 1's counted before.
12 else if (consecOnesCount > 0) {
13 operationsCount += 1; // We need an operation to separate the consecutive 1's.
14
15 // Reset consecOnesCount or set to 1 based on whether we had a single consecutive one or more.
16 consecOnesCount = (consecOnesCount === 1) ? 0 : 1;
17 }
18
19 // Right shift n to check the next bit.
20 n >>= 1;
21 }
22
23 // If there's a single 1 left.
24 if (consecOnesCount === 1) {
25 operationsCount += 1; // We'll need one more operation.
26 }
27 // If there are more than one consecutive 1's left.
28 else if (consecOnesCount > 1) {
29 operationsCount += 2; // We'll need two more operations to handle them.
30 }
31
32 // Return the total operations count.
33 return operationsCount;
34}
35
Time and Space Complexity
The time complexity of the provided code is O(log n)
because the primary operation within the while
loop is a bitwise right shift on n
, which effectively divides n
by 2. The number of iterations is directly proportional to the number of bits in the binary representation of n
, which is log n
base 2.
The space complexity of the code is O(1)
since the extra space used is constant, irrespective of the size of n
. The variables ans
and cnt
use a fixed amount of space during the execution of the algorithm.
Learn more about how to find time and space complexity quickly using problem constraints.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
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
LeetCode 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 we
Want a Structured Path to Master System Design Too? Don’t Miss This!