from typing import List
class Solution:
def diffWaysToCompute(self, expression: str) -> List[int]:
memo = {}
def ways(expr):
if expr in memo:
return memo[expr]
res = []
for i, c in enumerate(expr):
if c in "+-*":
left = ways(expr[:i])
right = ways(expr[i+1:])
for l in left:
for r in right:
if c == '+':
res.append(l + r)
elif c == '-':
res.append(l - r)
else:
res.append(l * r)
if not res:
res.append(int(expr))
memo[expr] = res
return res
return ways(expression)
#include <vector>
#include <string>
#include <unordered_map>
using namespace std;
class Solution {
public:
unordered_map<string, vector<int>> memo;
vector<int> diffWaysToCompute(string expression) {
if (memo.count(expression)) return memo[expression];
vector<int> res;
for (int i = 0; i < expression.size(); ++i) {
char c = expression[i];
if (c == '+' || c == '-' || c == '*') {
vector<int> left = diffWaysToCompute(expression.substr(0, i));
vector<int> right = diffWaysToCompute(expression.substr(i+1));
for (int l : left) {
for (int r : right) {
if (c == '+') res.push_back(l + r);
else if (c == '-') res.push_back(l - r);
else res.push_back(l * r);
}
}
}
}
if (res.empty()) res.push_back(stoi(expression));
memo[expression] = res;
return res;
}
};
import java.util.*;
class Solution {
Map<String, List<Integer>> memo = new HashMap<>();
public List<Integer> diffWaysToCompute(String expression) {
if (memo.containsKey(expression)) return memo.get(expression);
List<Integer> res = new ArrayList<>();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == '+' || c == '-' || c == '*') {
List<Integer> left = diffWaysToCompute(expression.substring(0, i));
List<Integer> right = diffWaysToCompute(expression.substring(i + 1));
for (int l : left) {
for (int r : right) {
if (c == '+') res.add(l + r);
else if (c == '-') res.add(l - r);
else res.add(l * r);
}
}
}
}
if (res.isEmpty()) res.add(Integer.parseInt(expression));
memo.put(expression, res);
return res;
}
}
/**
* @param {string} expression
* @return {number[]}
*/
var diffWaysToCompute = function(expression) {
const memo = {};
function ways(expr) {
if (memo[expr]) return memo[expr];
let res = [];
for (let i = 0; i < expr.length; i++) {
const c = expr[i];
if (c === '+' || c === '-' || c === '*') {
const left = ways(expr.slice(0, i));
const right = ways(expr.slice(i + 1));
for (const l of left) {
for (const r of right) {
if (c === '+') res.push(l + r);
else if (c === '-') res.push(l - r);
else res.push(l * r);
}
}
}
}
if (res.length === 0) res.push(Number(expr));
memo[expr] = res;
return res;
}
return ways(expression);
};
Given a string expression
representing a valid mathematical expression containing numbers and the operators '+'
, '-'
, and '*'
, your task is to compute all possible results from computing all the different ways to group numbers and operators using parentheses.
Each grouping can change the order of operations, so you must consider all possible ways to add parentheses and evaluate the results for each grouping.
expression
contains only digits and the operators '+'
, '-'
, and '*'
.At first glance, the problem seems to ask us to evaluate a mathematical expression. However, the twist is that we are supposed to consider all possible ways to add parentheses and compute the result for each arrangement. This means we need to explore every way the expression could be split into sub-expressions, recursively.
A brute-force approach would be to try every possible split at every operator, recursively compute the results for the left and right parts, and then combine them according to the operator. However, since many sub-expressions may repeat, this can lead to redundant computations.
To optimize, we can use memoization to cache and reuse results of sub-expressions we've already computed. This helps avoid recomputation and speeds up the solution.
This approach ensures that we explore all valid parenthesizations and efficiently avoid redundant computations.
Let's walk through the input expression = "2*3-4*5"
:
This process continues recursively, ensuring all possible groupings are considered.
n
numbers, the number of ways to fully parenthesize (i.e., number of binary trees) is exponential (specifically, related to Catalan numbers). Thus, the time complexity is roughly O(2^n)
in the worst case.O(n^2)
(since each is defined by a start and end index), and each sub-expression may combine results in O(n)
time. Thus, the overall time complexity is reduced to O(n^3)
.O(n^2)
entries, each potentially holding multiple results, so the space is also O(n^3)
in the worst case.The problem asks us to compute all possible results from different parenthesis groupings in a mathematical expression. By breaking the problem into smaller subproblems and using recursion with memoization, we efficiently explore all possible groupings and avoid redundant calculations. This recursive, divide-and-conquer strategy, enhanced by caching, makes the solution both elegant and efficient.