Taro Logo

Expression Add Operators

Hard
Meta logo
Meta
Topics:
StringsRecursion

Given a string num that contains only digits and an integer target, return all possibilities to insert the binary operators '+', '-', and/or '*' between the digits of num so that the resultant expression evaluates to the target value.

Note that operands in the returned expressions should not contain leading zeros.

For example:

  • num = "123", target = 6. Output: ["1*2*3","1+2+3"]
  • num = "232", target = 8. Output: ["2*3+2","2+3*2"]
  • num = "3456237490", target = 9191. Output: []

Explain your approach, especially how to handle the operator precedence and avoid leading zeros. Discuss the time and space complexity of your solution.

Solution


Naive Solution: Brute Force

We can use a brute-force approach to generate all possible expressions by inserting '+', '-', or '*' between the digits of the input string num. We'll evaluate each expression and check if it equals the target value.

  1. Generate Expressions: Recursively build expressions by inserting operators between digits.
  2. Evaluate Expressions: Evaluate each generated expression to get the result.
  3. Compare with Target: If the result matches the target, add the expression to the result list.

This approach will generate all possible combinations, but is very inefficient due to the exponential nature of possible expressions.

Optimal Solution: Backtracking

We can optimize the solution using backtracking. The idea is to explore possible expressions by recursively building them, but with a more efficient evaluation method. We'll keep track of the current expression, the current result, and the last operand used for multiplication.

  1. Base Case: If we have processed all digits and the current result equals the target, add the expression to the result list.

  2. Recursive Step: For each possible split point in the remaining digits:

    • Create a new operand.
    • Try adding the operand to the expression.
    • Try subtracting the operand from the expression.
    • Try multiplying the operand with the previous result.
  3. Backtracking: After each recursive call, undo the changes to explore other possibilities.

This approach is more efficient than the naive solution because it avoids generating and evaluating unnecessary expressions. The key optimization is the use of the prevOperand variable to correctly handle multiplication. Without this the algorithm will fail on test cases such as 1+2*3 because it will evaluate the 1+2 before multiplying by 3.

Edge Cases:

  • Leading Zeros: Operands should not have leading zeros, except for the operand '0' itself.
  • Integer Overflow: The intermediate results can be large, so use long to prevent integer overflow.

Code:

import java.util.ArrayList;
import java.util.List;

class Solution {
    public List<String> addOperators(String num, int target) {
        List<String> result = new ArrayList<>();
        if (num == null || num.length() == 0) {
            return result;
        }
        helper(result, "", num, target, 0, 0, 0);
        return result;
    }

    private void helper(List<String> result, String expression, String num, int target,
                        int pos, long currentResult, long prevOperand) {
        if (pos == num.length()) {
            if (currentResult == target) {
                result.add(expression);
            }
            return;
        }

        for (int i = pos; i < num.length(); i++) {
            if (i != pos && num.charAt(pos) == '0') {
                break; // Avoid leading zeros
            }
            long currentOperand = Long.parseLong(num.substring(pos, i + 1));

            if (pos == 0) {
                helper(result, String.valueOf(currentOperand), num, target, i + 1,
                        currentOperand, currentOperand);
            } else {
                helper(result, expression + "+" + currentOperand, num, target, i + 1,
                        currentResult + currentOperand, currentOperand);

                helper(result, expression + "-" + currentOperand, num, target, i + 1,
                        currentResult - currentOperand, currentOperand);

                helper(result, expression + "*" + currentOperand, num, target, i + 1,
                        currentResult - prevOperand + prevOperand * currentOperand, currentOperand * prevOperand);
            }
        }
    }
}

Time and Space Complexity:

  • Time Complexity: O(4^n), where n is the length of the input string. In the worst case, each digit can have 4 possibilities (no operator, +, -, *).
  • Space Complexity: O(n), where n is the length of the input string. This is due to the depth of the recursion stack.