动态规划公共子问题模式
在解决动态规划问题时候, 关键的一点就是找到子问题, 在动态规划中有一些常见的子问题模式,需要熟悉。下面就是一些常见子问题模式总结
1. 输入为x1,x2,···, xn输出为x1,x2, ···, xi
在这种情况下,子问题是从x1~xi这一部分。
子问题的个数一般为n, 所以用一个dp[n]的数组就能把子问题的解保存
例子:LeetCode746 Min Cost Climbing Stairs(爬上楼梯的最小损失)
博客地址:
https://blog.csdn.net/qq874455953/article/details/82564279
此题利用的就是求当前的dp[i], 要用到前面的dp[i-1]和dp[i-2]. 也就是说子问题是由前面的x1~xi-1得到的。
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
//定义一个数组 表示爬上每一层楼梯 需要的最少cost
vector<int> dp(cost.size(), 0);
//第一层楼梯和第二层楼梯的cost就是本身
dp[0] = cost[0];
dp[1] = cost[1];
//现在循环计算 后面每一层的最小cost
for (int i = 2; i < cost.size(); i++) {
dp[i] = min(dp[i-2] + cost[i], dp[i-1] + cost[i]);
}
//到达楼梯终点的最小cost 就是倒数第一层和第二层的最小cost值
return min(dp[cost.size()-2] , dp[cost.size()-1]);
}
};
2. 输入为x1,x2,···, xn和y1,y2,···, yn, 输出为x1,x2, ···, xi和y1,y2,···, yi
在这种情况下, 子问题个数一般为m * n, 所以用dp[m][n] 就能把子问题的解保存
例子:编辑距离
博客地址
https://blog.csdn.net/qq_36124194/article/details/83716115
输入为两个字符串, 也就是x1 ··· xm , y1 ··· yn ,解的结果是在两个字符串前面的子字符串产生。
class Solution {
public:
int minDistance(string word1, string word2) {
int size1 = word1.size();
int size2 = word2.size();
vector<vector<int>> count(size1+1, vector<int>(size2+1, 0));
for(int i = 0; i <= size1; i++) {
count[i][0] = i;
}
for(int i = 0; i <= size2; i++) {
count[0][i] = i;
}
for(int i = 1; i <= size1; i++) {
for(int j = 1; j <= size2; j++) {
int tag = 0;
if(word1[i-1] != word2[j-1]) {
tag = 1;
}
count[i][j] = min(count[i-1][j]+1, count[i][j-1]+1);
count[i][j] = min(count[i][j], tag+count[i-1][j-1]);
}
}
return count[size1][size2];
}
};
3. 输入为x1,x2,···, xn, 输出为xi,xi+1,···, xj.
这种情况子问题是 中间的一部分, 所以情况是n*n, 子问题的个数为n^2, 所以用**dp[n][n]**就能把子问题的解保存
4.输入为树, 输出为子树
例子:矩阵链式相乘
假设有4个矩阵A,B,C,D, 每个矩阵的维度依次是50 * 20, 20 * 1, 1 * 10, 10 * 100, 在计算A * B * C * D 的过程中我们可能会利用矩阵相乘的结合率来优化计算,比如我们可能会这样计算 (A * B * C) * D 或者 A * ( B * C )* D , 会得到很多种计算方式,其中有没有什么方式计算量最少呢?
我们可以看到
不同方式会产生巨大差异
其实我们可以把不同的计算顺序看成一颗二叉树, 例如
那么子问题就是这个树的子树 动态规划就变成了求子树的最优值。