Facebook Pixel

397. Integer Replacement

Problem Description

You are given a positive integer n. Your goal is to reduce this number to 1 using the minimum number of operations possible.

The allowed operations are:

  • If n is even, divide it by 2 (replace n with n / 2)
  • If n is odd, you can either add 1 or subtract 1 (replace n with n + 1 or n - 1)

You need to find the minimum number of operations required to transform n into 1.

For example:

  • If n = 8: You can do 8 → 4 → 2 → 1, which takes 3 operations
  • If n = 7: You can do 7 → 8 → 4 → 2 → 1 (using +1 first) or 7 → 6 → 3 → 2 → 1 (using -1 first), with the second path taking 4 operations

The solution uses bit manipulation to make optimal decisions. When n is even, it always divides by 2 (right shift by 1). When n is odd, it checks the last two bits: if both are 1 (meaning n & 3 == 3), it's generally better to add 1 (except for the special case when n = 3, where subtracting 1 is optimal). Otherwise, it subtracts 1. This greedy approach ensures we create more trailing zeros in the binary representation, allowing for more efficient divisions by 2 in subsequent steps.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight comes from looking at the binary representation of numbers. When we divide an even number by 2, we're essentially removing a trailing zero from its binary form. This is the most efficient operation since it directly reduces the number.

For odd numbers, we need to make a choice between adding or subtracting 1. Let's think about what happens in binary:

  • Subtracting 1 from an odd number turns the last bit from 1 to 0, making it even
  • Adding 1 to an odd number also makes it even, but might create a different pattern

The crucial observation is about consecutive 1s in the binary representation. When we have a number ending in 11 in binary (like 3, 7, 11, 15...), adding 1 creates a cascading effect. For example:

  • 7 in binary is 111. Adding 1 gives 1000 (8)
  • 11 in binary is 1011. Adding 1 gives 1100 (12)

Notice how adding 1 to numbers ending in 11 creates more trailing zeros, which means we can perform more divisions by 2 in subsequent steps. This is why when n & 3 == 3 (checking if the last two bits are both 1), we prefer to add 1.

However, there's a special case: when n = 3. Here, 3 → 2 → 1 (2 operations) is better than 3 → 4 → 2 → 1 (3 operations), so we subtract instead.

The pattern n & 3 checks the last two bits:

  • If it equals 3 (11 in binary), we generally add 1 to create more zeros
  • If it equals 1 (01 in binary), we subtract 1 to immediately get an even number

This greedy strategy of maximizing trailing zeros in the binary representation minimizes the total number of operations needed to reach 1.

Solution Approach

The implementation uses a greedy algorithm with bit manipulation to efficiently determine the optimal operation at each step.

Here's the step-by-step breakdown of the algorithm:

  1. Initialize a counter: Start with ans = 0 to track the number of operations.

  2. Main loop: Continue until n becomes 1:

    • Check if even: Use (n & 1) == 0 to test if n is even. This bitwise AND operation checks if the least significant bit is 0.
      • If even, perform right shift: n >>= 1 (equivalent to n = n // 2)
    • Handle odd numbers: When n is odd, make the decision between adding or subtracting 1:
      • Check if n != 3 and (n & 3) == 3:
        • (n & 3) masks the last two bits of n
        • If the result equals 3 (binary 11), it means both of the last two bits are 1
        • In this case, add 1: n += 1
      • Otherwise (when last two bits are 01 or when n = 3):
        • Subtract 1: n -= 1
    • Increment counter: After each operation, increment ans += 1
  3. Return the result: Once n reaches 1, return the total count of operations.

The bit manipulation techniques used:

  • n & 1: Checks if a number is odd (returns 1) or even (returns 0)
  • n & 3: Extracts the last two bits of the number
  • n >>= 1: Right shift by 1 position, equivalent to integer division by 2

This approach ensures O(log n) time complexity since we're essentially reducing the number by at least half every two operations in the worst case. The space complexity is O(1) as we only use a constant amount of extra space.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through the algorithm with n = 15 to see how the bit manipulation strategy works:

Step 1: n = 15 (binary: 1111)

  • Is 15 even? No, 15 & 1 = 1
  • Check last two bits: 15 & 3 = 3 (binary 11)
  • Since n ≠ 3 and last two bits are 11, we add 1
  • n becomes 16, operations = 1

Step 2: n = 16 (binary: 10000)

  • Is 16 even? Yes, 16 & 1 = 0
  • Divide by 2 (right shift): 16 >> 1 = 8
  • n becomes 8, operations = 2

Step 3: n = 8 (binary: 1000)

  • Is 8 even? Yes, 8 & 1 = 0
  • Divide by 2: 8 >> 1 = 4
  • n becomes 4, operations = 3

Step 4: n = 4 (binary: 100)

  • Is 4 even? Yes, 4 & 1 = 0
  • Divide by 2: 4 >> 1 = 2
  • n becomes 2, operations = 4

Step 5: n = 2 (binary: 10)

  • Is 2 even? Yes, 2 & 1 = 0
  • Divide by 2: 2 >> 1 = 1
  • n becomes 1, operations = 5

Result: 5 operations total

Path taken: 15 → 16 → 8 → 4 → 2 → 1

Notice how adding 1 to 15 (111110000) created four trailing zeros, allowing us to perform four consecutive divisions by 2. This demonstrates why the algorithm prefers adding 1 when the last two bits are 11 - it creates more opportunities for efficient division operations.

Solution Implementation

1class Solution:
2    def integerReplacement(self, n: int) -> int:
3        """
4        Find minimum number of operations to reduce n to 1.
5        Operations allowed: if n is even, divide by 2; if n is odd, add or subtract 1.
6      
7        Strategy:
8        - If even: always divide by 2
9        - If odd: check last two bits
10          - If ends in 11 (binary) and n != 3: add 1 (creates more trailing zeros)
11          - Otherwise: subtract 1
12        """
13        operations_count = 0
14      
15        while n != 1:
16            # Check if n is even (last bit is 0)
17            if (n & 1) == 0:
18                # Divide by 2 using right shift
19                n >>= 1
20            # Check if last two bits are 11 (binary) and n is not 3
21            # This means n % 4 == 3, which benefits from adding 1
22            elif n != 3 and (n & 3) == 3:
23                n += 1
24            else:
25                # For n == 3 or when last two bits are 01
26                # Subtracting 1 is optimal
27                n -= 1
28          
29            operations_count += 1
30      
31        return operations_count
32
1class Solution {
2    /**
3     * Finds the minimum number of operations to reduce n to 1.
4     * Operations allowed: if n is even, divide by 2; if n is odd, add or subtract 1.
5     * 
6     * @param n The positive integer to reduce to 1
7     * @return The minimum number of operations required
8     */
9    public int integerReplacement(int n) {
10        int operationCount = 0;
11      
12        // Continue until n becomes 1
13        while (n != 1) {
14            // Check if n is even (last bit is 0)
15            if ((n & 1) == 0) {
16                // If even, divide by 2 using unsigned right shift
17                n >>>= 1;
18            } 
19            // Check if n is odd and last two bits are both 1 (binary ends with 11)
20            // Exception: when n is 3, we should decrement instead of increment
21            else if (n != 3 && (n & 3) == 3) {
22                // Increment n (this leads to more consecutive zeros after division)
23                ++n;
24            } 
25            // For other odd numbers (binary ends with 01) or when n is 3
26            else {
27                // Decrement n
28                --n;
29            }
30          
31            // Increment the operation counter
32            ++operationCount;
33        }
34      
35        return operationCount;
36    }
37}
38
1class Solution {
2public:
3    int integerReplacement(int N) {
4        int steps = 0;
5        long num = N;  // Use long to avoid overflow when incrementing INT_MAX
6      
7        while (num != 1) {
8            // If num is even, divide by 2 (right shift by 1)
9            if ((num & 1) == 0) {
10                num >>= 1;
11            }
12            // If num is odd
13            else {
14                // Special case: for 3, always decrement (3 -> 2 -> 1 is optimal)
15                // For other numbers ending in binary '11', increment is better
16                // This is because adding 1 to '...11' gives '...00', allowing more divisions
17                if (num != 3 && (num & 3) == 3) {
18                    ++num;
19                }
20                // For numbers ending in binary '01' or when num is 3, decrement
21                else {
22                    --num;
23                }
24            }
25            ++steps;
26        }
27      
28        return steps;
29    }
30};
31
1function integerReplacement(N: number): number {
2    let steps: number = 0;
3    // Use a larger number type to avoid overflow when incrementing large values
4    let num: number = N;
5  
6    while (num !== 1) {
7        // If num is even, divide by 2 (right shift by 1)
8        if ((num & 1) === 0) {
9            num >>= 1;
10        }
11        // If num is odd
12        else {
13            // Special case: for 3, always decrement (3 -> 2 -> 1 is optimal)
14            // For other numbers ending in binary '11', increment is better
15            // This is because adding 1 to '...11' gives '...00', allowing more divisions
16            if (num !== 3 && (num & 3) === 3) {
17                num++;
18            }
19            // For numbers ending in binary '01' or when num is 3, decrement
20            else {
21                num--;
22            }
23        }
24        steps++;
25    }
26  
27    return steps;
28}
29

Time and Space Complexity

Time Complexity: O(log n)

The algorithm repeatedly divides n by 2 when it's even, or adjusts it by ±1 when it's odd. In the worst case, we need to consider how many operations are required to reduce n to 1.

  • When n is even, we perform a right shift (n >>= 1), which is equivalent to dividing by 2.
  • When n is odd, we either add or subtract 1, making it even, then in the next iteration divide by 2.

Since each pair of operations (when dealing with odd numbers) effectively reduces n by approximately half, and we're repeatedly halving the number until it reaches 1, the total number of iterations is proportional to log₂ n. Even in cases where we add 1 (like when n & 3 == 3), this is a strategic choice that leads to more consecutive divisions by 2, maintaining the logarithmic behavior.

Space Complexity: O(1)

The algorithm uses only a constant amount of extra space:

  • Variable ans to count the number of operations
  • Variable n is modified in-place
  • No additional data structures, recursion, or dynamic allocations are used

All operations are performed using a fixed number of variables regardless of the input size.

Common Pitfalls

1. Integer Overflow When Using Addition

One of the most common pitfalls in this problem is not considering integer overflow when adding 1 to large odd numbers. In languages with fixed integer sizes (like Java or C++), when n is close to the maximum integer value and you perform n + 1, it can cause overflow.

Example Problem Case:

  • In Java/C++, if n = Integer.MAX_VALUE (2,147,483,647), adding 1 would cause overflow
  • The binary representation of MAX_VALUE is 01111111111111111111111111111111
  • Since it ends in 11, the algorithm would try to add 1, causing overflow

Solution: Instead of actually modifying n by adding or subtracting, you can simulate the operations or use unsigned integers:

class Solution:
    def integerReplacement(self, n: int) -> int:
        operations_count = 0
      
        # Handle potential overflow by using long or treating as unsigned
        while n != 1:
            if (n & 1) == 0:
                n >>= 1
            elif n == 3:
                # Special case: for n=3, subtracting is better
                n -= 1
            elif (n & 3) == 3:
                # Instead of n += 1, we can directly divide by 2 twice
                # This avoids the intermediate addition step
                n = (n + 1) >> 1
                operations_count += 1  # Count the +1 operation
            else:
                n -= 1
          
            operations_count += 1
      
        return operations_count

2. Forgetting the Special Case n = 3

Another common mistake is not handling n = 3 as a special case. Without this check, the algorithm would choose to add 1 (since 3 in binary is 11), leading to the path 3 → 4 → 2 → 1 (3 operations) instead of the optimal 3 → 2 → 1 (2 operations).

Incorrect Implementation:

# Wrong: Missing n == 3 check
if (n & 3) == 3:  # This would incorrectly add 1 when n = 3
    n += 1
else:
    n -= 1

Correct Implementation:

# Correct: Explicitly check for n == 3
if n != 3 and (n & 3) == 3:
    n += 1
else:
    n -= 1

3. Misunderstanding the Bit Pattern Check

Some might incorrectly check only the last bit instead of the last two bits when deciding between adding or subtracting for odd numbers. The pattern (n & 3) == 3 checks if the number ends in 11 in binary, which indicates that adding 1 would create more consecutive zeros for efficient division.

Why checking two bits matters:

  • Numbers ending in 01 (like 5, 9, 13): subtracting 1 gives a number ending in 00
  • Numbers ending in 11 (like 7, 11, 15): adding 1 gives a number ending in 00
  • Having more trailing zeros allows for more consecutive divisions by 2
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input 56?

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More