LCS问题 | 最长公共子序列 动态规划算法总结

前言:

    lcs问题目前做到了这两道。我学了第一道解题之后,第二道直接ac出来,解法类似,因此此篇主要介绍第一题解题思想。

Oj链接-求最长公共子序列的长度

Oj链接-求出最长公共子序列的字符串

目录

一、解题思想- 动态规划

1.dp[i][j]的含义

2.讨论dp[i][j]情况

 3.初始化小技巧

4.填表顺序

5.返回结果 

二、 第2题思想-求出最长公共字符串

三、代码部分 

1.第一题:

2.第二题


一、解题思想- 动态规划

1.dp[i][j]的含义

        建一个二维表dp[i][j],i 表示第一个字符串的长度范围j 表示第二个字符串的长度范围是范围,而不是一定要用i / j字符。相当于是不同范围排列组合列一个表。 

       而dp[i][j]表示对第一个字符串的范围截取到[0,i]  对于第二个字符串的范围截取到[0,j] 它们的最大公共子序列的长度。

      以下第一个字符串统称s1,第二个字符串统称s2。

     例如dp[2][3]表示,对于s1,只能用前两个字符构成的子序列,不一定要用上第二个字符对于s2只能用前三个字符构成子序列。而dp[2][3]表示它们的最大公共子序列的长度。   

2.讨论dp[i][j]情况

a:当s1[ i ] == s2[ j ]时

    dp[ i ][ j ]就是包括第一个字符串范围可以到i,第二个字符串范围可以到j。

    如果s1[i]==s2[j],dp[i-1][j-1]就可以拼上一样的字符构成新的最长子序列。

    因此dp[i][j]=dp[i-1][j-1]+1.

b:当s1[ i ] != s2[ j ]时

有3种情况:

dp[ i ][ j ]=dp[ i ][ j-1 ](不要s2的第j个字符,我们剩下的自己玩)

dp[ i ][ j ]=dp[ i-1][ j ](不要s1的第i个字符,我们剩下的自己玩)

dp[ i ][ j ]=dp[ i-1][ j-1 ](即不要s1的第i个字符,s2的第j个字符,我们剩下的自己玩!)

 根据dp表的定义,dp[ i ][ j ]=dp[ i-1 ][ j-1 ] 其实被其他两种情况包括了,我们可以去掉这种情况。因此dp[ i ][ j ]等于剩下两种情况的max值。

 因此dp[i][j]=max(dp[ i ][ j-1 ],dp[ i-1][ j ])

 3.初始化小技巧

对字符串前面加一个空格:

对字符串进行特殊处理  :前面加一个空格

 s1=" "+s1; s2=" "+s2;

原因:

      在这道题里,空字符串是有研究意义的,当两个当没有公共子序列时,它们的公共子序列就是"",而这个""的长度为0。实际意义上来说,对于后续的dp,也只是等于在前面拼接上一个""(空字符串),不影响解题。

      引入这个这个空格是为了简化边界条件的处理。

      想象一下,若没加上一个空字符,那么dp[ 0 ][ 0 ]就是s1的第1个字符和s2的第1个字符的范围内的最大子序列。是无法用上面的公式,因为公式涉及[ i -1 ][ j -1],数组越界,那我们就要单独处理这种情况很麻烦。

  • dp[0][i] 表示 s1 的前 0 个字符(即空字符串)与 s2 的前 i 个字符的最长公共子序列长度。显然,空字符串与任何字符串的最长公共子序列长度都是 0,所以 dp[0][i] = 0
  • 同理,dp[i][0] 表示 s1 的前 i 个字符与 s2 的前 0 个字符(即空字符串)的最长公共子序列长度,也应该是 0。

初始化代码:dp 数组(int[ ][ ])在Java中会自动初始化为 0,因此这部分可省略。

4.填表顺序

公式总结:

a:当s1[ i ] == s2[ j ]时,dp[ i ][ j ]=dp[ i-1 ][ j-1 ]+1.

b:当s1[ i ] != s2[ j ]时,dp[ i ][ j ]=max(dp[ i ][ j-1 ],dp[ i-1][ j ])

根据公式,当我们填dp[ i ][ j ]时,需要如图所示三个量。

因此填表顺序:从左往右,从上往下。

5.返回结果 

根据dp的定义可得,返回dp[ s1.length() ][ s2.length() ];

二、 第2题思想-求出最长公共字符串

跟第一题很像!!(看代码就明白了)

细节:注意Java里的String默认为空,所以要把第一行、第一列的初始化为""(空字符串)。

三、代码部分 

1.第一题:


//最长公共序列长度
public class test1 {
  
    public static int longestCommonSubsequence(String s1, String s2) {
        s1=" "+s1;
        s2=" "+s2;

        int len1=s1.length();
        int len2=s2.length();

        int[][] dp=new int[len1][len2];


        //初始化空串情况 - 其实可以省略 默认初始化好为0了
        for(int i=0;i<len1;i++){
            dp[i][0]=0;
        }

        for(int i=0;i<len2;i++){
            dp[0][i]=0;
        }


        //填表
       for(int i=1;i<len1;i++){
            for(int j=1;j<len2;j++){
                if(s1.charAt(i)==s2.charAt(j)){
                    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[len1-1][len2-1];

    }
}

2.第二题

import java.util.*;


public class Solution {
    public String LCS (String s1, String s2) {
   
        //那题第1题一样的思路,只是把个数换成字符串

        s1=" "+s1;
        s2=" "+s2;

        int len1=s1.length();
        int len2=s2.length();

        String[][] dp=new String[len1][len2];

        //初始化""
        for(int i=0;i<len1;i++){
            dp[i][0]="";
        } 
        for(int i=0;i<len2;i++){
            dp[0][i]="";
        }

        //填充dp表
        for(int i=1;i<len1;i++){
            for(int j=1;j<len2;j++){
                if(s1.charAt(i) == s2.charAt(j)){
                    dp[i][j]=dp[i-1][j-1]+s1.charAt(i);
                }else{
                    dp[i][j]=dp[i-1][j].length()>dp[i][j-1].length()?dp[i-1][j]:dp[i][j-1];
                }
            }
        }

     
       if(dp[len1-1][len2-1]==null||dp[len1-1][len2-1]==""){
        return "-1";
       }
       return  dp[len1-1][len2-1];
    }
}

  • 26
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值