LeetCode1143 最长公共子序列

38 阅读2分钟

leetcode.cn/problems/lo… image.png

解法一:自顶向下的递归DP+备忘录

image.png 因为我们在求最大值嘛,情况三在计算 s1[i+1..] 和 s2[j+1..] 的 lcs 长度,这个长度肯定是小于等于情况二 s1[i..] 和 s2[j+1..] 中的 lcs 长度的,因为 s1[i+1..] 比 s1[i..] 短嘛,那从这里面算出的 lcs 当然也不可能更长嘛。同理,情况三的结果肯定也小于等于情况一。说白了,情况三被情况一和情况二包含了,所以我们可以直接忽略掉情况三。

func longestCommonSubsequence(text1 string, text2 string) int {
	memo := make([][]int, len(text1))
	// 备忘录初始化为 -1,避免和可能答案冲突
	for i := range memo {
		memo[i] = make([]int, len(text2))
		for j := range memo[i] {
			memo[i][j] = -1
		}
	}
	return dp(text1, 0, text2, 0, memo)
}

// dp[i][j]表示 s1[i...]和s2[j...]的最长公共子序列长度
func dp(s1 string, p1 int, s2 string, p2 int, memo [][]int) int {
	if p1 == len(s1) || p2 == len(s2) { // base case, 有一个子串已经是空串了
		return 0
	}
	if memo[p1][p2] != -1 { // 备忘录避免重复计算
		return memo[p1][p2]
	}
	if s1[p1] == s2[p2] {
		// s1[i] 和 s2[j] 这个字符必然在 lcs 中,
		// 再加上 s1[i+1..] 和 s2[j+1..] 中的 lcs 长度,就是答案
		// 更新到备忘录中
		memo[p1][p2] = dp(s1, p1+1, s2, p2+1, memo) + 1
	} else {
		// s1[i] 和 s2[j] 至少有一个不在 lcs 中
		// 在可能的选项中,取最大值,更新到备忘录中
		// 这里排除了dp(s1, p1+1, s2, p2+1)这一种情况,因为肯定比下面2种长度更短
		memo[p1][p2] = max(
			dp(s1, p1+1, s2, p2, memo),
			dp(s1, p1, s2, p2+1, memo),
		)
	}
	return memo[p1][p2]
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}