这一部分,对已经做过的技术类型的dp问题进行总结。
LeetCode62. 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?
示例 1:
输入: m = 3, n = 2
输出: 3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右
示例 2:
输入: m = 7, n = 3
输出: 28
此题是一道典型的计数型动态规划,针对的是二维空间。如果机器人到达grid[i][j]的位置,由于它只能向右或者向左运动,则他的上一步座标可能为grid[i - 1][j]或grid[i][j - 1],即上一步只存在两种可能性,那么最多路径呢则为右移和下移路径可能性之和。
我们令 dp[i][j] 是到达 i, j 最多路径,可以很容易得出动态方程:dp[i][j] = dp[i-1][j] + dp[i][j-1],注意,对于第一行dp[0][j],或者第一列dp[i][0],由于都是在边界,所以只能为1。
class Solution {
public:
int uniquePaths(int m, int n) {
vector<vector<int>> dp(m, vector<int>(n, 0));
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(i == 0 || j == 0) dp[i][j] = 1;
else dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
};
LeetCode剑指 Offer 47. 礼物的最大价值
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
示例 1:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
此题为上一题的变形,需要统计所过路径的最大礼物价值。思路同上,在到达grid[i][j]的位置时,由于它只能向右或者向左运动,则他的上一步座标可能为grid[i - 1][j]或grid[i][j - 1],即上一步只存在两种可能性,由于计算最大值,我们则需要取这两种路径移动的礼物最大值加上当前位置的礼物价值,作为该位置移动路径的礼物最大值。
即得到状态方程:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j],并将实时更新路径移动的最大价值,这里我们需要注意初始化i = 0, j = 0的情况,即第一行和第一列状态的初始化问题。
class Solution {
public:
int maxValue(vector<vector<int>>& grid) {
int row = grid.size();
int column = grid[0].size();
int ans = INT_MIN;
vector<vector<int>> dp(row, vector<int>(column, 0));
for(int i = 0; i < row; i++){
for(int j = 0; j < column; j++){
if(i == 0 && j == 0) dp[i][j] = grid[i][j];
else if(i == 0) dp[i][j] = dp[i][j - 1] + grid[i][j]; //初始横向移动
else if(j == 0) dp[i][j] = dp[i - 1][j] + grid[i][j]; //初始纵向移动
//上一步是横向移动和纵向移动得到的最大路径:max(dp[i - 1][j], dp[i][j - 1])
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
ans = max(ans, dp[i][j]);
}
}
return ans;
}
};
LeetCode72. 编辑距离
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
动态规划思路:
看到这道题,我们很自然知道有3个选择,可以替换、删除或者增加字符。但是怎么利用呢?
将大问题拆分为小问题,即面对word1的前i个要变为word2的前j个字符时,我们需要消耗多少步呢?
替换的情况:当word1中的前i-1个就可以变换为word2中的前j-1个,那么我们只要根据word1中的第i个是否等于word2中的第j个字符进行判断,如果相等,那么dp[i][j]=dp[i - 1][j - 1];否则,dp[i][j]=dp[i - 1][j - 1] + 1,加的1就是我们将word1中第i个字符替换为word2中第j个的消耗。
删除的情况:当word1中的前i - 1个就可以变换为word2中的前j个时,我们需要将word1中的第i个字符删除,dp[i][j]=dp[i - 1][j]+1
增加的情况:当word1中的前i个可以变换为word2中的前j - 1个时,我们需要将word1中的第i个字符后面增加一个,dp[i][j]=dp[i][j - 1]+1
所以,我们的dp[i][j]取上列的最小值即可:dp[i][j] = min(dp[i - 1][j], min(dp[i][j - 1], dp[i - 1][j - 1])) + 1。
ps:注意,我们的dp数组索引均是从1开始,而word中索引是0开始的,所以word1[i - 1] == word2[j - 1],就是在判断我们word1中的第i个字符是否等于word2中第j个字符。即:dp[i][j] = min(dp[i][j], dp[i - 1][j - 1])。
示例的演示过程如下:
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.size();
int n = word2.size();
// 有一个字符串为空串
if (n * m == 0) return n + m;
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for(int i = 0; i < m + 1; i++)
dp[i][0] = i;
for(int j = 0; j < n + 1; j++)
dp[0][j] = j;
for(int i = 1; i < m + 1; i++){
for(int j = 1; j < n + 1; j++){
int del = dp[i - 1][j]; //增
int add = dp[i][j - 1]; //删
int mdf = dp[i - 1][j - 1]; //改
dp[i][j] = min(dp[i - 1][j], min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
if(word1[i - 1] == word2[j - 1])
dp[i][j] = min(dp[i][j], dp[i - 1][j - 1]);
}
}
return dp[m][n];
}
};