动态规划经典问题 - 求最长公共子串 / 公共子序列

首先要理解公共子串和公共子序列

公共子串和公共子序列是两个不相同的概念, 公共子串要求字符连续, 公共子序列不要求字符连续。
比如: 字符串"ascde", “axcxdde”
最长公共子串是 “de” , 因为 "de"在两个字符串中都是连续的
最长公共子序列是 “acde”, 不要求连续, 只要满足两个字符串中子序列前后顺序的一致即可

动态规划求最长公共子序列

动态规划的核心是

  • base case
  • 状态转移方程
  • 自下而上

我们可以通过状态转移表来帮助我们得出状态转移方程
这里我直接说结论:
我们思考一个问题, 如果我们求 字符串 s1的前 i 位子串和 s2的前 j 位子串的最长公共子序列 f(i,j), 跟我们求 s1的前 i-1 位子串和 s2的前 j-1 位子串的最长公共子序列 f(i-1,j-1) 两者之间有什么关系呢?
或者换一种说法, 如果我们知道的 f(i-1,j-1) 的结果 x, 我们如何根据 x 推得 f(i,j) 呢?

其结论就是, 我们判断 s1.charAt(i) 和 s2.charAt(j) 是否相等, 如果相等, 则 f(i,j) = f(i-1, j-1) + 1;
如果不相等, f(i,j) = max(f(i-1, j), f(i, j-1));

这个结论我就不证明了, 这不需要我们开发工程师深入探索。
如果你理解了上面的公式, 我们就可以进入编码阶段。
在这里插入图片描述
代码实现有两种思路, 一是回溯, 二是动态规划
回溯就是所谓的自上而下, 遇到分支的时候去探索每一个分支, 当遇到末尾的时候回溯到上一分支。动态规划是所谓的自下而上, 先解决子问题, 再根据子问题的结果去解决父问题。动态规划的优势在于, 通过备忘录避免对子问题的重复求解, 缺点和很显然, 用空间换时间, 占用内存。

这里我们用动态规划实现
首先我们需要解决 base case, 我的理解就是边界条件, 也就是 i=0 或者 j=0时的情况
在这里插入图片描述
很显然, i=0时, 我们遍历j, 当s1.charAt(i) == s2.charAt(j) 时, f(0,j)=1, j=0时同理

        for (int i = 0; i < s1.length(); i++) {
   
            if (s1.charAt(i) == s2.charAt(0)) {
   
                arr[i][0] = 1;
                max = 1;
            }
        }
        for (int j = 0; j < s2.length(); j++) {
   
            if (s1.charAt(0) == s2.charAt(j)) {
   
                arr[0][j] = 1;
                max = 1;
            }
        }

我们把子问题的解缓存到 int[i][j] 的数组arr中, 避免重复求解
整体的题解如下

public class Test2 {
   

    static String s1 = 
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code tea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值