基础
加油!!!
刷题
- 不同的子序列
动规五部曲:
- 确定dp数组含义
dp[i][j] 以 s[j-1]为结尾的字符串 中 以t[i-1]为结尾的字符串的 出现个数
- 确定dp数组递推公式
if s[j-1] == t[i-1] {
dp[i][j] = dp[i-1][j-1] + dp[i][j-1]
// 这里举个例子
// s 为 rabb, t为rab
// 最后一个元素b相等,dp[i-1][j-1]就是 s为 rab , t为ra 的出现个数,是1
// 这里没有考虑 s为 rab ,t为 rab, 就是dp[i][j-1]
// 当 s为rabbb, t为rab
// 也是两种情况:
// s为rabb, t为ra
// s为rabb, t为rab,这种情况是第一个例子。第一个例子已经考虑了 s为 rab ,t为 rab
// 所以就不需要深入到 dp[i][j-2]了
// 这就是递归的意义
}else {
dp[i][j] = dp[i][j-1]
// 需要画图理解
}
- 确定dp数组初始化
dp[0][j] = 1
dp[i][0] = 0. i!= 0
//画图理解
- 确定dp数组遍历顺序
从上到下,从左到右
- 打印dp数组
- 两个字符串的删除操作
这个是求两个字符串最长公共子序列的思路,反推出最小需要删除的操作次数
第二个直接使用最小删除次数作为dp数组的value
动规五部曲(第二种方法):
- 确定dp数组含义
dp[i][j] 以word1[i-1]为结尾的字符串 和 以word2[j-1]为结尾的字符串变成相同,需要删除的次数
- 确定dp数组递推公式
if word1[i-1] == word2[j-1] {
dp[i][j] = dp[i-1][j-1]
}else {
dp[i][j] = min(dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1]+2)
// 这里当word1[i-1] != word2[j-1]的时候
// dp[i-1][j] 是删除word1 最后一个字母的最小删除次数, 因为我们已经考虑删除了一个字母,故+1
//第二个一样
//dp[i-1][j-1]+2 可以不写,因为前两个已经包含
}
- 确定dp数组初始化
当word1 = ‘’, word2需要全部删除才能保持一样
dp[0][j] = j
dp[i][0] = i
- 确定dp数组遍历顺序
从左到右,从上到下
- 打印dp数组
- 编辑距离 (经典题目)
动规五部曲:
- 确定dp数组含义
dp[i][j] 以word1[i-1]为结尾的字符串 转换成 word2[j-1]为结尾的字符串需要的最小操作次数
- 确定dp数组递推公式
if word1[i-1] == word2[j-1] {
dp[i][j] = dp[i-1][j-1]
}else {
//三种操作方式
//删除 删除word1,即 dp[i-1][j]+1
//替换 替换word1最后一个字母和word2保持一致,即 dp[i-1][j-1]+1
//增加 增加word1的元素,和word2保持一致,即 dp[i][j-1] + 1
dp[i][j] = min(dp[i-1][j-1]+1, dp[i-1][j]+1, dp[i][j-1]+1)
}
- 确定dp数组初始化
dp[i][0] = i
dp[0][j] = j
- 确定dp数组遍历顺序
从左到右,从上到下
- 打印dp数组
总结
- 动态规划核心思想是记录递归的状态,而不需要每一次都从头遍历,需要dp[2]中记录dp[1],这样dp[3]就不会从dp[1]开始计算,而是直接拿dp[2]