思路
CODE和官方解析一样,主要借此地方整理一下动态规划,并用一个具体例子,捋顺思路。
动态规划用于解决具有重叠子问题 和最优子结构特性的问题。
一这个题为例,我们希望找到给定算术表达式的所有可能计算结果,这个问题可以分为一系列子问题,在这个题里,子问题就是————计算表达式不同部分得到的解。
首先我们达到计算字符串,形式的算术表达式。有运算符有数字,我们通过添加括号,改变运算优先顺序,然后得到所有可能的结果。
动态规划算法可以归为以下步骤,对照Code,可以被划分为四个部分。
解析表达式
对标一开始的初始化,要注意,数字,可能不止一位数字,所以循环套循环。
值得注意的一点:
在这道题 奇数索引位置代表运算符 ;偶数索引位置代表数字。
初始化动态规划列表
我们使用一个二维向量 dp 来存储结果,其中 dp[i][j] 存储子表达式 ops[i] 到 ops[j] 所有可能的计算结果。
填充动态规划表
动态规划过程的核心是填充这个 dp 表。我们从最小的子表达式开始(只包含单个数字),然后逐渐增加子表达式的大小。
最小子表达式:对于单个数字的子表达式(不包含任何运算符),dp[i][i] 只有一个可能的结果,即它自己。
增加子表达式大小:当子表达式的长度增加时,我们将其分成两部分:左边的子表达式和右边的子表达式。我们在可能的每个位置 k 分割子表达式,k 是位于 l 和 r 之间的任何运算符的位置。
对于每个分割点 k,我们结合 dp[l][k-1](左边所有可能的结果)和 dp[k+1][r](右边所有可能的结果)以及 ops[k](分割点处的运算符)来生成新的结果,并将这些结果添加到 dp[l][r]。
获取最终结果
动态规划过程完成后,dp[0][ops.size() - 1] 将包含整个表达式的所有可能结果。
示例
以表达式 2 * 3-4 * 5 为例:
解析表达式后,我们得到序列 [2, '', 3, '-', 4, '', 5]。
初始化 dp,每个数字自己就是一个结果,所以 dp[i][i] 就是 [2], [3], [4], [5]。
而且这地方应该是:
dp[0][0] = [2] // 只有一个数字2
dp[1][1] = [] // 位置1是运算符,没有单独的计算结果
dp[2][2] = [3] // 只有一个数字3
。。。。
dp[4][4] = [4] // 只有一个数字 4
dp[6][6] = [5] // 只有一个数字 5
我们开始填充 dp,首先是长度为 3 的子表达式,所以从i = 3 开始,比如 2 * 3, 3 - 4, 4 * 5。
2*3,只有一个计算方法。
dp[0][2] = [6] //ops的位置[0, 2]
dp[2][4] = [-1] // ops的位置[2, 4]
dp[4][6] = [20] //ops的位置[4, 6]
然后是长度为 5 的子表达式,比如 2 * 3 - 4, 3- 4 * 5,直到我们计算整个表达式 2 * 3- 4 * 5。
dp[0][4] = [-2,2]//ops的位置[0, 4], dp[0][2] - dp[4][4] 的组合,[0][0] * [2][4]
dp[2][6] = [-17,-5]//ops的位置[2, 6], dp[2][2] - dp[4][6] 的组合 24 * 66
最后
dp[0,6] = [-34,-10,-14,-10, 10] //00*26 02-46 04*66
在动态规划的每一步,我们都在考虑不同的分割点,查看如果在这个点添加括号会如何影响计算结果,然后将所有的结果合并。
通过这种方式,我们能够高效地计算出所有可能的结果,因为每个子表达式的结果都是基于更小子表达式结果的组合,避免了重复计算。
Code
C++
class Solution {
public:
const int ADD = -1;
const int SUB = -2;
const int MULTI = -3;
vector<int> diffWaysToCompute(string expression) {
vector<int>ops;
// initialization 最初初始化
//将输入字符串expression解析为一个队列ops,其中包含数字和运算符(这里运算符用特殊的整数值表示)
for(int i = 0; i<expression.size();){
//运算符
if(!isdigit(expression[i])){
if(expression[i]=='+'){ops.push_back(ADD);}
else if(expression[i] == '-'){ops.push_back(SUB);}
else if(expression[i] == '*'){ops.push_back(MULTI);}
i++;
}else{
int t = 0;
// 因为是string
while(i < expression.size() && isdigit(expression[i])){
t = t*10 + expression[i] - '0';
i++;
}
ops.push_back(t);
//如果expression是“123+456”,那么代码首先会解析出123并将其添加到ops,然后处理`+号,在遇到 456 时重复的步骤。最终ops将包含之前[123, ADDITION, 456],其中ADDITION是一个特殊的值
// 所以expression数字和运算符交替存在
}
}
// dp 初始化动态规划列表
// 初始化规划动态存储dp,其数量是n x n,n是ops存储的容量。
vector<vector<vector<int>>> dp((int)ops.size(),vector<vector<int>>((int)ops.size()));
// 最小子表达式
for(int i = 0; i < ops.size(); i+=2){
dp[i][i] = {ops[i]};
}
//增加子表达式大小
for(int i =3;i<=ops.size();i++){
for(int j = 0; j+i <=ops.size();j+=2){
int l = j;
int r = j+i-1;
for(int k = j + 1; k < r; k+=2){
auto left = dp[l][k-1];
auto right = dp[k+1][r];
for (auto& num1:left){
for(auto& num2:right){
if(ops[k] == ADD){
dp[l][r].push_back(num1+num2);
}else if(ops[k] == SUB){
dp[l][r].push_back(num1 - num2);
}else if(ops[k] == MULTI){
dp[l][r].push_back(num1 * num2);
}
}
}
}
}
}
// 得出最终结论
return dp[0][(int) ops.size() - 1];
}
};