Given a string expression
of numbers and operators, return all possible results from computing all the different possible ways to group numbers and operators. You may return the answer in any order.
The test cases are generated such that the output values fit in a 32-bit integer and the number of different results does not exceed 104
.
Example 1:
Input: expression = "2-1-1" Output: [0,2] Explanation: ((2-1)-1) = 0 (2-(1-1)) = 2
Example 2:
Input: expression = "2*3-4*5" Output: [-34,-14,-10,-10,10] Explanation: (2*(3-(4*5))) = -34 ((2*3)-(4*5)) = -14 ((2*(3-4))*5) = -10 (2*((3-4)*5)) = -10 (((2*3)-4)*5) = 10
Constraints:
1 <= expression.length <= 20
expression
consists of digits and the operator '+'
, '-'
, and '*'
.[0, 99]
.'-'
or '+'
denoting the sign.This problem asks to find all possible results from computing all different ways to group numbers and operators in a given expression. The solution uses recursion with memoization to efficiently explore all possible parenthesizations.
The core idea is to recursively divide the expression at each operator. For each operator, we recursively compute all possible results for the left and right sub-expressions. Then, we combine the results using the operator to get all possible results for the current expression. Memoization (using a cache) is crucial to avoid redundant computations of sub-expressions.
Base Case: If the expression is a single number (no operators), convert it to an integer and return it as a single-element list.
Recursive Step:
+
, -
, or *
) is found:
Memoization: Store the results for each unique sub-expression in a cache (dictionary or map) to avoid recomputation. The key is the sub-expression string, and the value is the list of results.
Time Complexity: O(N * 2N), where N is the number of operators (and operands). The recursion tree branches at each operator, potentially leading to an exponential number of subproblems. The memoization improves performance significantly, but the worst-case time complexity remains exponential due to the potential for the vast number of different ways to parenthesize the expression.
Space Complexity: O(N * 2N) in the worst case, primarily due to the space used by the recursion stack and the memoization cache. The cache could theoretically store results for all possible sub-expressions.
The Python code efficiently implements this approach using Python's @cache
decorator for memoization.
class Solution:
def diffWaysToCompute(self, expression: str) -> List[int]:
@cache
def dfs(exp):
if exp.isdigit():
return [int(exp)]
ans = []
for i, c in enumerate(exp):
if c in '-+*':
left, right = dfs(exp[:i]), dfs(exp[i + 1 :])
for a in left:
for b in right:
if c == '-':
ans.append(a - b)
elif c == '+':
ans.append(a + b)
else:
ans.append(a * b)
return ans
return dfs(expression)
The dfs
function recursively explores all possible ways to add parentheses. The @cache
decorator automatically memoizes the results, significantly improving efficiency. The base case handles expressions consisting of a single number. The recursive step iterates through the expression, finding operators, and recursively processing the left and right sub-expressions.
The other language solutions (Java, C++, Go, C#) follow a similar structure, with appropriate adjustments for the syntax and data structures of each language. The use of a memo
dictionary or map is crucial for performance.