线性DP不像背包问题有模板,少数常见的线性 DP 问题也有模板,比如LIS 和 LCS 问题
目录
问题描述:
给定两个序列X <x1,x2,x3...xm> 和Y <y1,y2,y3...yn>,求他们最长的公共子序列
如X="ABCBDAB" Y="BDCABA" ans="BCBA";
暴力方法的运行时间为指数阶,不实用。
使用动态规划来解:
分析:
设Z=<z1,z2,z3....zk> 是 X<x1,x2,x3...xm> Y<y1,y2,y3...yn> 的LCS
则有三种情况
第一种情况:如果xm = yn,则zk = xm = yn 且 Zk-1是Xm-1和Yn-1的一个LCS
第一种情况:如果xm != yn 且 zk != xm,则Z是Xm-1和Y的一个LCS
第一种情况:如果xm != yn 且 zk != yn,则Z是X和Yn-1的一个LCS
我们可以定义c[i,j]便是Xi和Yi的LCS长度,可以得到:
下面代码:
过程中还需要维护一个数组b帮助构造最优解
计算LCS的长度
int[][] b = new int[m+1][n+1];
int[][] c = new int[m+1][n+1];
//初始化上边和左边为0
for(int i=0;i<=m;i++) c[i][0]=0;
for(int i=0;i<=n;i++) c[0][i]=0;
//从1开始,左边和右边初始化为0
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
//这里注意s的坐标要-1,因为ij是从1开始的,但是字符串是从0开始的
if(s1[i-1]==s2[j-1])
{
c[i][j] = c[i-1][j-1]+1;
b[i][j] = 1; //1表示箭头左上
}
//比较上和左,哪方大就等于谁
else if(c[i-1][j] >= c[i][j-1]) //左大
{
c[i][j] = c[i-1][j];
b[i][j] = 2; //2表示箭头向左
}
else //上大
{
c[i][j] = c[i][j-1];
b[i][j] = 3; //3表示箭头向右
}
}
}
图中阴影序列中每个“↖”对应的表项表示xi==yj是LCS的一个元素
c[m][n]保存了LCS的长度,时间复杂度为O(mn)
逆序递归构造LCS
public static void printLCS(int[][] b,char[] s1,int m,int n)
{
if(m==0 || n==0) return;
if(b[m][n]==1)
{
printLCS(b,s1,m-1,n-1);
System.out.print(s1[m-1]); //记得字符串-1,因为是从0开始的
}
else if(b[m][n]==2) printLCS(b,s1,m-1,n);
else printLCS(b,s1,m,n-1);
}
时间复杂度为O(m+n) ,结果打印出BCBA
完整代码+注释
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException{
//BufferedReader scan = new BufferedReader(new InputStreamReader(System.in));
Scanner scan = new Scanner(System.in);
String s1 = "ABCBDAB";
String s2 = "BDCABA";
LCS(s1,s2);
}
public static void LCS(String S1,String S2)
{
char[] s1 = S1.toCharArray();
char[] s2 = S2.toCharArray();
int m = s1.length;
int n = s2.length;
int[][] b = new int[m+1][n+1];
int[][] c = new int[m+1][n+1];
//初始化上边和左边为0
for(int i=0;i<=m;i++) c[i][0]=0;
for(int i=0;i<=n;i++) c[0][i]=0;
//从1开始,左边和右边初始化为0
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
//这里注意s的坐标要-1,因为ij是从1开始的,但是字符串是从0开始的
if(s1[i-1]==s2[j-1])
{
c[i][j] = c[i-1][j-1]+1;
b[i][j] = 1; //1表示箭头左上
}
//比较上和左,哪方大就等于谁
else if(c[i-1][j] >= c[i][j-1]) //左大
{
c[i][j] = c[i-1][j];
b[i][j] = 2; //2表示箭头向左
}
else //上大
{
c[i][j] = c[i][j-1];
b[i][j] = 3; //3表示箭头向右
}
}
}
//打印c数组,c[m][n]即为LCS长度
/*for(int i=0;i<=m;i++)
{
for(int j=0;j<=n;j++)
{
System.out.print(c[i][j]+" ");
}
System.out.println();
}*/
for(int i=0;i<=m;i++)
{
for(int j=0;j<=n;j++)
{
System.out.print(b[i][j]+" ");
}
System.out.println();
}
//逆序递归构造LCS
printLCS(b,s1,m,n);
}
public static void printLCS(int[][] b,char[] s1,int m,int n)
{
if(m==0 || n==0) return;
if(b[m][n]==1)
{
printLCS(b,s1,m-1,n-1);
System.out.print(s1[m-1]); //记得字符串-1,因为是从0开始的
}
else if(b[m][n]==2) printLCS(b,s1,m-1,n);
else printLCS(b,s1,m,n-1);
}
}