Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

964. Least Operators to Express Number - Leetcode Solution

Code Implementation

class Solution:
    def leastOpsExpressTarget(self, x: int, target: int) -> int:
        from functools import lru_cache

        @lru_cache(None)
        def dp(i, target):
            if target == 0:
                return 0
            if target == 1:
                return i  # x / x if i > 0, else x^0
            if i >= 39:
                return float('inf')
            t, r = divmod(target, x)
            # Option 1: use r times x^i, plus the rest
            cost1 = r * i + dp(i + 1, t)
            # Option 2: use (x - r) times negative x^i, plus the rest (+1 for operator)
            cost2 = (x - r) * i + dp(i + 1, t + 1)
            if i == 0:
                return min(cost1, cost2 + 1)
            return min(cost1, cost2 + 1)
        return dp(0, target) - 1
      
class Solution {
public:
    unordered_map memo;
    int x;

    int dp(int i, long long target) {
        if (target == 0) return 0;
        if (target == 1) return i;
        if (i >= 39) return INT_MAX / 2;
        long long key = target * 100 + i;
        if (memo.count(key)) return memo[key];

        long long t = target / x, r = target % x;
        int cost1 = r * i + dp(i + 1, t);
        int cost2 = (x - r) * i + dp(i + 1, t + 1);
        int res = min(cost1, cost2 + 1);
        memo[key] = res;
        return res;
    }

    int leastOpsExpressTarget(int x, int target) {
        this->x = x;
        return dp(0, target) - 1;
    }
};
      
class Solution {
    Map<String, Integer> memo = new HashMap<>();

    public int leastOpsExpressTarget(int x, int target) {
        return dp(x, 0, target) - 1;
    }

    private int dp(int x, int i, int target) {
        if (target == 0) return 0;
        if (target == 1) return i;
        if (i >= 39) return Integer.MAX_VALUE / 2;
        String key = target + "," + i;
        if (memo.containsKey(key)) return memo.get(key);

        int t = target / x, r = target % x;
        int cost1 = r * i + dp(x, i + 1, t);
        int cost2 = (x - r) * i + dp(x, i + 1, t + 1);
        int res = Math.min(cost1, cost2 + 1);
        memo.put(key, res);
        return res;
    }
}
      
var leastOpsExpressTarget = function(x, target) {
    const memo = new Map();

    function dp(i, target) {
        if (target === 0) return 0;
        if (target === 1) return i;
        if (i >= 39) return Number.MAX_SAFE_INTEGER;
        let key = target + ',' + i;
        if (memo.has(key)) return memo.get(key);

        let t = Math.floor(target / x), r = target % x;
        let cost1 = r * i + dp(i + 1, t);
        let cost2 = (x - r) * i + dp(i + 1, t + 1);
        let res = Math.min(cost1, cost2 + 1);
        memo.set(key, res);
        return res;
    }
    return dp(0, target) - 1;
};
      

Problem Description

Given two positive integers x and target, you are to express target using only the number x and the operators +, -, *, and /. Each operator can be used any number of times, but you cannot use parentheses and must respect the usual operator precedence (multiplication and division before addition and subtraction). You may use x as many times as needed, and can use powers of x (like x * x, x * x * x, etc.).

Your goal is to find the least number of operators needed to express target in terms of x.

Constraints:

  • There is always at least one valid solution.
  • Operators are counted; using x itself does not count as an operator.
  • Division is integer division.

Thought Process

At first glance, it might seem we could just build up target using repeated multiplications and additions of x. However, as target can be very large, a brute-force approach of trying all combinations is not feasible.

The key realization is that every number can be represented in base x. If we write target in base x, each digit tells us how many times we need to use a certain power of x (i.e., x^i).

For each digit, we have two choices:

  • Use the digit times x^i (costs digit * number of operators to write x^i), and solve for the rest.
  • Use (digit - x) times x^i in the negative direction, and add one to the next higher power (i.e., handle carry/borrow).
We need to minimize the total number of operators across all choices.

Solution Approach

  • Step 1: Dynamic Programming (DP) Recursion
    • Define a recursive function dp(i, target) that returns the minimal number of operators needed to express target using powers of x starting from x^i.
    • Base cases:
      • If target == 0, no operators needed.
      • If target == 1, it takes i operators to write x / x / ... / x (i times).
    • For each recursive call:
      • Divide target by x to get quotient t and remainder r.
      • Option 1: Use r times x^i, costing r * i operators, then solve for dp(i+1, t).
      • Option 2: Use (x - r) times x^i in the negative direction, costing (x - r) * i operators, then solve for dp(i+1, t+1) (carry over).
      • Pick the minimum of these two options.
  • Step 2: Memoization
    • To avoid recomputation, store results for each (i, target) in a memoization table or cache.
  • Step 3: Operator Counting
    • When i == 0, using x^0 (which is 1) requires a special case: we must use x / x, which uses one operator.
    • Subtract 1 from the final answer since the first x in the highest power does not require an operator.
  • Step 4: Implementation
    • Implement the recursive function with memoization in your chosen language.
    • Start the recursion with i = 0 and the given target.

Example Walkthrough

Let's use x = 3 and target = 19 as an example.

  1. Write 19 in base 3: 19 = 2 * 3^2 + 0 * 3^1 + 1 * 3^0 (so digits are [1,0,2], from lowest to highest power).
  2. For the lowest power (x^0), digit is 1. We can write this as x / x (costs 1 operator).
  3. For x^1, digit is 0. No need to use x^1.
  4. For x^2, digit is 2. We can write this as x * x + x * x (costs 2 * 2 = 4 operators: two multiplications and one addition).
  5. Add up the costs: 1 (for x^0) + 0 (for x^1) + 4 (for x^2) = 5 operators, but the recursion may find a more optimal way by considering negative representations (using subtraction and carrying over).
  6. The DP explores both using the remainder and "borrowing" from the next power, ensuring the minimal operators are used.

The DP solution automatically considers all options and finds the minimum, which for this example is 5.

Time and Space Complexity

  • Brute-force:
    • Would involve trying all possible combinations of operators and powers, leading to exponential time complexity: O(2^N), where N is the number of powers considered.
  • Optimized DP Approach:
    • We only consider up to logx(target) powers (since x^i > target for large i).
    • Each state is defined by (i, target), and with memoization, the number of unique states is O(log(target) * log(target)), since at most log(target) levels and target reduces by a factor of x at each step.
    • Thus, time and space complexity are both O(logx(target) * logx(target)).

Summary

The problem leverages the relationship between number representation in base x and the minimal use of arithmetic operators. By using dynamic programming and memoization, we efficiently explore all possible ways to build the target number using the least operators. The key insight is to recursively break down the problem by powers of x, always considering both using the remainder and borrowing from the next higher power, ensuring the optimal solution is found. This approach transforms a seemingly brute-force problem into a tractable one by smart state management and recursion.