【动态规划】最长公共子序列

最长公共子序列

给出两个序列text1和text2,要求求出这两个序列的最长公共子序列和最长公共子序列长度。

**最长公共子序列的含义:**这里的子序列可以不是连续的,只要相对位置是符合的就算,比如abdfs和adef的最长公共子序列是adf。

例题:求出acfsc(text1)和acs(text2)的最长公共子序列。

1️⃣.划分子问题

对于求text1和text2的最长公共子序列,从只有一个字符时候开始求解,求出只有一个字符的时候的最长公共序列,然后扩大问题,求出前两个字符的最长公共子序列,不断扩大问题规模,直到达到最大规模。

2️⃣.确定动态规划方程

这里先列出求长度时的模拟过程,通过模拟的过程分析出如何递推,找出动态规划方程。

dp表格,最长公共子序列长度表格

text1|text2acs
0000
a0111
c0122
f0122
s0123
c0123

第二行:t1只有a和t2取a,这时候相等,那么长度就是1,t1不变,由于t1只有一个元素a,所以第二行都为1;

第三行:t1取ac,t2取a,这个时候t1的c和t2的c不相等,但是t1的前一个a和t2的a相等,所以取dp[i] [j]的上方的值dp[i-1] [j]填充到当前值。

第三行:t1取ac,t2取acs,这个时候c和s不等,当前值的左方值是更大,t1的ac和t2的ac,所以取左方dp[i] [j-1]填充到dp[i] [j]。

通过观察表格不难发现,当前值dp[i] [j]是由三个方向推出来的,左上方,左方,上方,进一步可以确定动态规划方程。

  • t1[i]==t2[j]

d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 , t e x t 1 [ i ] = = t e x t 2 [ j dp[i][j] = dp[i-1][j-1]+1,text1[i]==text2[j dp[i][j]=dp[i1][j1]+1text1[i]==text2[j

  • t1[i]!=t2[j]

d p [ i ] [ j ] = M a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) , t e x t 1 [ i ] ! = t e x t 2 [ j ] dp[i][j]=Max(dp[i-1][j],dp[i][j-1]),text1[i]!=text2[j] dp[i][j]=Max(dp[i1][j],dp[i][j1]),text1[i]!=text2[j]

为了方便递推,dp数组的第一行和第一列设置为0,也就是t1和t2是空串的时候,同样,由于dp数组对应的字符的i和j不是从0开始,所以t1和t2数组使用i和j索引的时候要注意避免越界。

3️⃣:递推填写表格

确定动态规划方程后,通过双重for循环便可以实现表格的填写,dp数组的右下角的值即为t1和t2的最长公共子序列长度。

4️⃣:回溯求最长公共子序列

填写表格求出最长公共子序列后还需要求出是那些字符组成,为了实现这个功能,还需要再用一个二维数组state来存当前dp数组求解中每个值是如何推出的状态,这里定为1(t1[i]==t2[j] 从左上角推出),2(从左边推出),3(从上方推出),当然标志的定义是随意的,只要可以达到区分的目的即可。

5️⃣:注意

明确dp数组的含义:dp[i] [j]代表t1[i]和t2[j]所组成的最长公共子序列长度。

💻:代码实现

package com.dp; import java.util.Scanner; /** * @Author Lunau * @Create 2022-04-21 16:07 * @Description * @Result */ public class CommonOrder { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入序列1:"); char[] ach = sc.nextLine().toCharArray(); System.out.println("请输入序列2"); char[] bch = sc.nextLine().toCharArray(); System.out.println("最长公共序列长度为:"+getCommonOrder(ach,bch)); } public static int getCommonOrder(char[] ach,char[] bch) { //最长公共子序列数组 第一行第一列初始化为0 int[][] dp = new int[ach.length+1][bch.length+1]; //序列状态数组 int[][] state = new int[ach.length+1][bch.length+1]; for(int i=1;i<=ach.length;i++) { for(int j=1;j<=bch.length;j++) { //相等就在上一个基础上加一 if(ach[i-1]==bch[j-1]) { dp[i][j] = dp[i-1][j-1]+1; state[i][j] = 1; //dp[i][j]从左上方推出 } else if(dp[i][j-1]>=dp[i-1][j]) { //不相等就从dp[i-1][j]或dp[i][j-1]取较大者 dp[i][j] = dp[i][j-1]; state[i][j] = 2; //dp[i][j]从左边推出 } else{ dp[i][j] = dp[i-1][j]; state[i][j] = 3; //dp[i][j]从上方推出 } } } //求最长公共序列,dp数组右下角为最长公共子序列长度 int p=ach.length,q=bch.length,k=dp[p][q]; char[] res = new char[k]; //防止越界 while(p>0&&q>0) { if(state[p][q]==1) { res[k-1] = ach[p-1]; //ach最后一个索引是ach长度减一,res是从0开始索引 k--;p--;q--; } else if(state[p][q]==2) { q--; //bch指针左移 } else { p--; //ach指针左移动 } } for(int i=0;i<res.length;i++) { System.out.print(res[i]+" "); } return dp[ach.length][bch.length]; } }

__EOF__

本文作者lnnau
本文链接https://www.cnblogs.com/wlunan/p/18538042.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   Lnnau  阅读(46)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示