动态规划经典题目-最长公共子序列

一、题目描述

​ 求两个字符串s1和s2的最长公共子序列。

​ 这里说明下,子序列是可以不连续字符按顺序组成的字符串。比如字符串democrat的其中一个子序列是dmat。而mdat就不是它的子序列,因为字符出现顺序不符合。

示例:

输入:s1 = "democrat" s2 = "republican"
输出:eca

二、解题思路

​ 其实本题就是字符串编辑距离的变种,字符串编辑距离会了,此题就会了。就是字符串编辑距离去掉替换操作,就变成了此问题。

1. 定义状态

​ 设dp[i][j]表示字符串s1的前i个字符与字符串s2前j个字符的最长公共子序列。

2. 定义状态转移方程

当s1[i] == s2[j]时,有 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j] = dp[i-1][j-1] + 1 dp[i][j]=dp[i1][j1]+1

否则 d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j ] d p [ i ] [ j − 1 ] dp[i][j] = max\begin{cases} dp[i-1][j] \\ dp[i][j-1] \end{cases} dp[i][j]=max{dp[i1][j]dp[i][j1]

3. 初始化

​ 显然dp[0][j]初始化为0。

​ 显然dp[i][0]初始化为0。

三、代码实现

/**
 * 最大公共子序列
 *
 *  @author hh
 *  @date 2021-5-16 16:44
 */
public class LongestCommonSubSequence {

    public String commonSubSequence(String s1,String s2){
        //保存最长子序列
        List<Character> subSequences = new LinkedList<>();
        //定义dp数组
        int[][] dp = new int[s1.length() + 1][s2.length() + 1];
        //计算dp数组
        for(int i = 1 ; i <= s1.length();  i++){
            for(int j = 1; j <= s2.length(); j++){
                if(s1.charAt(i-1) == s2.charAt(j -1)){
                    dp[i][j] = dp[i-1][j-1] +1;
                    if(subSequences.size() < dp[i][j]){
                        subSequences.add(s1.charAt(i-1));
                    }
                }else{
                    dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return subSequences.stream().map(String::valueOf).collect(Collectors.joining());
    }

    public static void main(String[] args){
        String s1 = "democrat";
        String s2 = "republican";
        LongestCommonSubSequence longestCommonSubSequence = new LongestCommonSubSequence();
        System.out.println(longestCommonSubSequence.commonSubSequence(s1,s2));
    }
}

四、执行结果

在这里插入图片描述

五、举一反三

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-palindromic-subsequence
示例 1:

输入:s = “bbbab”
输出:4
解释:一个可能的最长回文子序列为 “bbbb” 。

示例 2:

输入:s = “cbbd”
输出:2
解释:一个可能的最长回文子序列为 “bb” 。

其实本题可以理解为最长公共子序列的变形,我们可以这么想回文子序列的正序和倒序是相同,那么我把字符串s反转表示为s',那么最终转化为求两字字符串的最长子序列的长度,代码实现如下所示:

public class Solution2 {

    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        //定义dp数组
        int[][] dp = new int[n + 1][n + 1];
        char[] cs1 = s.toCharArray();
        char[] cs2 = new StringBuilder(s).reverse().toString().toCharArray();
        //计算dp数组
        for(int i = 1 ; i <= n;  i++){
            for(int j = 1; j <= n; j++){
                if(cs1[i-1] == cs2[j -1]){
                    dp[i][j] = dp[i-1][j-1] +1;
                }else{
                    dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[n][n];
    }

    public static void main(String[] args){
        String s = "aabaaba";
        Solution2 solution = new Solution2();
        System.out.println(solution.longestPalindromeSubseq(s));
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值