第九章 动态规划part13
今天 我们就要结束动态规划章节了,大家激不激动!!!
详细布置
647. 回文子串
动态规划解决的经典题目,如果没接触过的话,别硬想 直接看题解。
programmercarl.com/0647.%E5%9B…
DP 数组 dp[i][j] 的含义
dp[i][j]表示字符串s中从索引i到j的子串s[i...j]是否是一个回文子串。- 如果
dp[i][j]为true,则子串s[i...j]是回文; - 如果
dp[i][j]为false,则子串s[i...j]不是回文。
- 如果
递推公式
要判断 dp[i][j] 是否为 true,需要满足以下条件:
-
如果
s[i] === s[j]且子串长度<= 2(即j - i <= 1):- 当
i和j相等时,意味着这是一个单字符子串,单字符必然是回文; - 当
j - i == 1时,表示子串长度为 2,只要两个字符相等,即是回文。 递推条件为: [ dp[i][j] = \text{true} \quad \text{if } s[i] === s[j] \text{ and } j - i <= 1 ]
- 当
-
如果
s[i] === s[j]且dp[i + 1][j - 1]为true:- 说明
s[i + 1 ... j - 1]子串已经是回文,并且s[i] === s[j],因此整个s[i ... j]也是回文。 递推条件为: [ dp[i][j] = \text{true} \quad \text{if } s[i] === s[j] \text{ and } dp[i + 1][j - 1] = \text{true} ]
- 说明
遍历顺序
- 需要从右往左遍历索引
i,因为dp[i][j]依赖于dp[i + 1][j - 1]的结果,所以要确保在计算dp[i][j]时,dp[i + 1][j - 1]已经被计算过。
/**
* @param {string} s
* @return {number}
*/
var countSubstrings = function (s) {
let dp = Array.from(Array(s.length), () => Array(s.length).fill(false));
let res = 0;
for (let i = s.length - 1; i >= 0; i--) {
for (let j = i; j < s.length; j++) { // j 从 i 开始
if (s[i] === s[j]) {
if (j - i <= 1) {
dp[i][j] = true;
res++;
} else if (dp[i + 1][j - 1]) {
dp[i][j] = true;
res++;
}
}
}
}
console.log(dp);
return res;
};
516.最长回文子序列
- 回文子串,求的是回文子串,而本题要求的是回文子序列, 大家要搞清楚两者之间的区别。
programmercarl.com/0516.%E6%9C…
DP 数组的定义
dp[i][j]表示字符串s中从索引i到j的子串的最长回文子序列的长度。
递推公式
- 如果
i === j,表示子串长度为 1,单个字符本身是回文,因此dp[i][j] = 1。 - 如果
s[i] === s[j],那么最长回文子序列长度可以通过dp[i + 1][j - 1] + 2得到,意思是去掉i和j两端字符后,剩下子串的最长回文子序列长度加上这两个匹配字符的长度 2。 - 如果
s[i] !== s[j],则dp[i][j]等于dp[i][j - 1]和dp[i + 1][j]的较大值,表示在这两个字符不相等的情况下,只能考虑去掉其中一个字符后的最长回文子序列长度。
遍历顺序
- 外层循环从字符串的末尾向前遍历
i,内层循环从i + 1开始向后遍历j,保证每次更新dp[i][j]时,依赖的dp[i + 1][j - 1]已经计算出来。
代码如下:
/**
* @param {string} s
* @return {number}
*/
var longestPalindromeSubseq = function (s) {
let dp = Array.from(Array(s.length), () => Array(s.length).fill(0));
// 每个单独的字符都是回文,初始化 dp[i][i] 为 1
for (let i = 0; i < s.length; i++) {
dp[i][i] = 1;
}
// 从后向前遍历字符串的每个字符 i
for (let i = s.length - 1; i >= 0; i--) {
// 遍历比 i 大的 j
for (let j = i + 1; j < s.length; j++) {
if (s[i] === s[j]) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]);
}
}
}
console.log(dp);
return dp[0][s.length - 1];
};
示例
假设输入 s = "bbbab",输出将是 4,因为最长回文子序列为 "bbbb"。
总结
dp[i][j]记录子串s[i...j]的最长回文子序列长度。- 当
s[i] === s[j]时,子序列长度为dp[i + 1][j - 1] + 2。 - 当
s[i] !== s[j]时,子序列长度为dp[i][j - 1]和dp[i + 1][j]的最大值。