LeetCode 分治法
@
241. 为运算表达式设计优先级
给定一个含有数字和运算符的字符串,为表达式添加括号,改变其运算优先级以求出不同的结果。你需要给出所有可能的组合的结果。有效的运算符号包含 +, - 以及 * 。
-
分治思想:
- 将加括号转化为,对每个运算符号,先执行处理两侧的数学表达式,再处理此运算符号。
- 利用递归将当前遍历符号的左右两侧表达式继续拆分。
- 边界情况下,字符串无运算符号,只有数字。
-
例:5 + 3 * 4 + 2
- left -> 5
- right -> 3 * 4 + 2
- left -> 3
- right -> 4 + 2
- left -> 4
- right -> 2
- left -> 3 * 4
- left -> 3
- right -> 4
- right -> 2
- left : 5, right: 3 * (4 + 2) = 18, (3 * 4) + 2 = 14
- (3 * 4 + 2); (3 * 4) + 2; 3 * (4 + 2); 前两种结果相同
class Solution {
public:
vector<int> diffWaysToCompute(string expression) {
int n = expression.length();
vector<int> ways;
for (int i = 0; i < n; 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 (const int& l : left) {
for (const int& r : right) {
switch(c) {
case '+': ways.push_back(l + r); break;
case '-': ways.push_back(l - r); break;
case '*': ways.push_back(l * r); break;
}
}
}
}
}
if (ways.empty()) ways.push_back(stoi(expression)); // 边界情况处理
return ways;
}
};
- 记忆化存储
- 某些被划分的字符串可能重复出现多次,可以使用hash map将这些结果存储起来,再次遍历到该字符串时直接查询存储的结果。
class Solution {
public:
unordered_map<string, vector<int>> strMap; // 外部建立哈希映射表
vector<int> diffWaysToCompute(string expression) {
int n = expression.length();
// 查看哈希表中是否存储了该表达式划分的结果
if (strMap.count(expression)) {
return strMap[expression];
}
vector<int> ways;
for (int i = 0; i < n; i++) {
char c = expression[i];
if (c == '+' || c == '-' || c == '*') {
vector<int> left = diffWaysToCompute(expression.substr(0, i));
// 将左侧表达式及其对应的结果放入哈希映射表中
pair<string, vector<int>> instance1(expression.substr(0, i), left);
strMap.insert(instance1);
vector<int> right = diffWaysToCompute(expression.substr(i + 1));
pair<string, vector<int>> instance2(expression.substr(i + 1), right);
strMap.insert(instance2);
for (const int& l : left) {
for (const int& r : right) {
switch(c) {
case '+': ways.push_back(l + r); break;
case '-': ways.push_back(l - r); break;
case '*': ways.push_back(l * r); break;
}
}
}
}
}
if (ways.empty()) ways.push_back(stoi(expression));
return ways;
}
};
- 动态规划:
- 从下而上处理
- dp[i][j][] 表示第 i 个数至第 j 个数的所有可能运算结果
- j = i -> 0 方向计算 dp[j][i];设 i = 3;j = 3,2,1,0;则先计算得到dp[3][3],dp[2][3],dp[1][3],dp[0][3];dp[2][3] = dp[2][2] * dp[3][3];dp[1][3] = dp[1][1] * dp[2][3]; dp[1][2] * dp[3][3];
- j = 0 -> i 方向计算 dp[j][i];设 i = 3;j = 0,1,2,3;则先计算dp[0][3],但是dp[0][3] = dp[0][0] * dp[1][3];dp[0][1] * dp[2][3]; dp[0][2] * dp[3][3];的结果,若按此顺序,dp[2][3]的结果还未计算,因此无法得到正确结果;
class Solution {
public:
vector<int> diffWaysToCompute(string expression) {
istringstream ss(expression + "+"); // 增加一个标志便于截止
vector<int> data;
vector<char> ops;
int num = 0;
char op = ' ';
// 获取表达式中的数字和运算符号
while (ss >> num && ss >> op) {
data.push_back(num);
ops.push_back(op);
}
int n = data.size();
vector<vector<vector<int>>> dp(n, vector<vector<int>>(n, vector<int>()));
for (int i = 0; i < n; i++) {
for (int j = i; j >= 0; j--) {
if (i == j) {
dp[j][i].push_back(data[i]); // 第 i 个数字本身
}else {
for (int k = j; k < i; k += 1) { // 自 j 向 i 步进,考虑所有情况
// 计算得到第 j 个数到第 i 个数的所有运算结果,其用 k 进行分割
// j 至 i 可以分为 j 至 k 与 k + 1 到 i
for (const int& left : dp[j][k]) {
for (const int& right : dp[k + 1][i]) {
int val = 0;
// 以 k 为分界,所以调用第 k 个运算符
switch(ops[k]) {
case '+' : val = left + right; break;
case '-' : val = left - right; break;
case '*' : val = left * right; break;
}
dp[j][i].push_back(val); // 存储结果
}
}
}
}
}
}
return dp[0][n - 1];
}
};