题目
先写状态方程
/** f(i, j) 表示第一个字符串从0到i的字符串(s[0...i])
* 和第二字符串从0到j的子字符串(s[0...j])
* 的最长公共子序列的长度
* 如:s1 和 s2 的 f(4, 4) = "bde".length */
/** 当 s[i] === s[j] => f(i, j) = f(i-1, j-1) + 1
* 最后一个字符串相等很显然只要知道 f(i-1, j-1) 的结果就知道 f(i, j) 的结果了 */
/** 当 s[i]! = s[j] => f(i, j) = max(f(i-1, j), f(i, j-1))
* 最后一个字符串不相等很显然最长的子串里面肯定不可能同时含有各自的最后一个字符串
* 所以比较一下 s1[0...i - 1]、s2[0...j] 和 s1[0...i]、s[0...j-1] 的最长子串
*/
自底向上解法
// 先写状态方程
/** f(i, j) 表示第一个字符串从0到i的字符串(s[0...i])
* 和第二字符串从0到j的子字符串(s[0...j])
* 的最长公共子序列的长度
* 如:s1 和 s2 的 f(4, 4) = "bde".length */
/** 当 s[i] === s[j] => f(i, j) = f(i-1, j-1) + 1
* 最后一个字符串相等很显然只要知道 f(i-1, j-1) 的结果就知道 f(i, j) 的结果了 */
/** 当 s[i]! = s[j] => f(i, j) = max(f(i-1, j), f(i, j-1))
* 最后一个字符串不相等很显然最长的子串里面肯定不可能同时含有各自的最后一个字符串
* 所以比较一下 s1[0...i - 1]、s2[0...j] 和 s1[0...i]、s[0...j-1] 的最长子串
*/
// 自底向上解法
const s1 = "abcde",
s2 = "badfe";
function getMax(s1, s2) {
const length1 = s1.length
const length2 = s2.length
// 这里为啥两个 length 都加1?f(0,0) 的情况无法拿 dp[-1, -1] 整一个结果其实都向前存储一格 f(i,j) 对应 dp(i+1, j+1)
const dp = new Array(length1+1).fill(new Array(length2+1).fill(0))
for(let i = 0; i < length1; i++) {
for(let j = 0; j < length2; j++) {
// 这个地方可以看出其实一个值的求解只依赖于
// 它的左上方的值dp[i][j],正上的值dp[i][j+1], 左边的值dp[i+1][j]
// 因此我们可以考虑空间优化,每次只保存两行的值就可以了,在下面给出空间优化解法
if (s1[i] === s2[j]) {
dp[i+1][j+1] = dp[i][j] + 1
} else {
dp[i+1][j+1] = Math.max(dp[i][j+1], dp[i+1][j])
}
}
}
return dp[length1][length2]
}
console.log(getMax(s1, s2));
空间优化写法
function getMax(s1, s2) {
const length1 = s1.length
const length2 = s2.length
const dp = new Array(2).fill(new Array(length2+1).fill(0)) // 改动
for(let i = 0; i < length1; i++) {
for(let j = 0; j < length2; j++) {
if (s1[i] === s2[j]) {
dp[(i+1)%2][j+1] = dp[i%2][j] + 1 // 改动
} else {
dp[(i+1)%2][j+1] = Math.max(dp[i%2][j+1], dp[(i+1)%2][j]) // 改动
}
}
}
return dp[length1%2][length2] // 改动
}
console.log(getMax(s1, s2));
tips: 对这部分空间优化不太理解的可以先看看我总结的爬楼梯问题,可以说是后面推出的这一系列问题的基础
后话
下周会对环形房屋偷盗问题和粉刷房子问题做一个总结 2022.5.22 22:34 上海浦东