Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

2139. Minimum Moves to Reach Target Score - Leetcode Solution

Problem Description

You are given two integers, target and maxDoubles. Your goal is to reach the target score starting from 1, using the minimum number of moves. In each move, you can:

  • Increment your current score by 1 (as many times as you want).
  • Double your current score (up to maxDoubles times in total).

Find the minimum number of moves required to reach target from 1. Each operation counts as one move. You cannot use more than maxDoubles double operations.

Constraints:

  • There is always at least one valid solution.
  • You cannot reuse a double operation after you have used up all maxDoubles.
  • 1 <= target <= 10^9
  • 0 <= maxDoubles <= 100

Thought Process

At first glance, the problem seems to encourage a brute-force approach: starting from 1, simulate every possible sequence of increments and doubles until you reach target. However, this would be extremely inefficient for large values of target.

The key insight is to realize that, since doubling multiplies your score by 2, it is much more powerful than incrementing by 1. Therefore, you want to use the double operation as much as possible, but only when it is most effective. Also, since you can only double a limited number of times (maxDoubles), you need to be strategic about when to use it.

Instead of building up from 1 to target, it is often easier to think in reverse: start from target and work backwards to 1, deciding whether the previous move was an increment or a double. This reverse thinking helps avoid unnecessary operations and makes the greedy strategy clear.

Solution Approach

We use a greedy, reverse approach to minimize the number of moves:

  1. Work backwards from target to 1: At each step, decide whether the previous operation was an increment or a double.
  2. If you still have double operations left (maxDoubles > 0):
    • If target is even, the last operation could have been a double. Divide target by 2 and use one double operation.
    • If target is odd, the last operation must have been an increment. Subtract 1 from target.
  3. If you have no double operations left:
    • You can only increment. The remaining number of moves is target - 1 (since you start at 1).
  4. Count the number of moves: Each operation (increment or double) counts as one move. Repeat until target becomes 1.

This approach is efficient because it always chooses the operation that brings target closer to 1 in the fewest possible moves, using doubles only when allowed and beneficial.

Example Walkthrough

Example: target = 10, maxDoubles = 4

  1. Start at target = 10, maxDoubles = 4, moves = 0
  2. 10 is even and maxDoubles > 0: double used, target = 5, maxDoubles = 3, moves = 1
  3. 5 is odd: must increment, target = 4, moves = 2
  4. 4 is even and maxDoubles > 0: double used, target = 2, maxDoubles = 2, moves = 3
  5. 2 is even and maxDoubles > 0: double used, target = 1, maxDoubles = 1, moves = 4
  6. Now target = 1: done.

Result: 4 moves are required.

Time and Space Complexity

Brute-force approach: Simulating all possible sequences from 1 to target would take exponential time, which is infeasible for large target.

Optimized (greedy, reverse) approach:

  • Each iteration reduces target by at least half (when doubling) or by 1 (when incrementing).
  • The number of steps is at most O(log(target)) for doubles, plus O(target) for increments if maxDoubles = 0. But since maxDoubles is small (≤ 100), the loop is very fast.
  • Space complexity is O(1) because we only use a few variables.

Summary

The problem is efficiently solved by thinking in reverse and greedily using double operations whenever possible. This approach minimizes the number of moves by leveraging the power of doubling, and only using increments when necessary. The solution is both efficient and elegant, requiring only a simple loop and constant space.

Code Implementation

class Solution:
    def minMoves(self, target: int, maxDoubles: int) -> int:
        moves = 0
        while target > 1 and maxDoubles > 0:
            if target % 2 == 0:
                target //= 2
                maxDoubles -= 1
            else:
                target -= 1
            moves += 1
        moves += target - 1
        return moves
      
class Solution {
public:
    int minMoves(int target, int maxDoubles) {
        int moves = 0;
        while (target > 1 && maxDoubles > 0) {
            if (target % 2 == 0) {
                target /= 2;
                maxDoubles--;
            } else {
                target -= 1;
            }
            moves++;
        }
        moves += target - 1;
        return moves;
    }
};
      
class Solution {
    public int minMoves(int target, int maxDoubles) {
        int moves = 0;
        while (target > 1 && maxDoubles > 0) {
            if (target % 2 == 0) {
                target /= 2;
                maxDoubles--;
            } else {
                target -= 1;
            }
            moves++;
        }
        moves += target - 1;
        return moves;
    }
}
      
var minMoves = function(target, maxDoubles) {
    let moves = 0;
    while (target > 1 && maxDoubles > 0) {
        if (target % 2 === 0) {
            target = Math.floor(target / 2);
            maxDoubles--;
        } else {
            target -= 1;
        }
        moves++;
    }
    moves += target - 1;
    return moves;
};