算法学习-LCS最长公共子序列模板(JAVA实现)

本文最后更新于:January 27, 2022 pm

纸上得来终觉浅,绝知此事要躬行。路漫漫其修远兮,吾将上下而求索!知识是经过历史的巨人沉淀下来的,别总想着自己能够快速学会,多花点时间去看看,也许会发现些不同的东西。你能快速学会的、觉得简单的东西,对于别人来说也是一样的。人外有人,天外有天。当努力到达了一定的程度,幸运自会与你不期而遇。

目录

求公共子序列长度

状态转移方程:

dp[i][j] = max(dp[i-1][j], dp[i][j-1],dp[i-1][j-1] + (A[i]==B[j] ? 1 : 0)),表示在这三种状态中取到最大值。

(1)第一种状态表示不录入第一个序列的第i个字符时的最长公共子序列,

(2)第二种状态表示不录入第二个序列的第j个字符时的最长公共子序列,

(3)第三种状态表示第一个序列的前i-1个字符与第二个序列前j-1个字符的公共子序列加上最后一个字符的录入状态,如果最后的一个字符相等则录入,状态为1,否则不录入,状态为0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;


/**
* @Author DragonOne
* @Date 2021/12/5 21:27
* @墨水记忆 www.tothefor.com
*/
public class Main {
public static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
public static StreamTokenizer cin = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
public static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
public static Scanner sc = new Scanner(System.in);

public static int maxd = 10000+7;
public static int INF = 0x3f3f3f3f;
public static int mod = 998244353;
public static int[][] dp = new int[maxd][maxd]; //dp[i][j]表示串a的前i个和串b的前j个的公共子序列的最长长度

public static void lcs(char[] x,char[] y){
int lenx = x.length;
int leny = y.length;
for(int i=1;i<=lenx;++i){
for(int j=1;j<=leny;++j){
if(x[i-1]==y[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]); //当当前对应位置不等时,就看是不要串a的,还是不要串b中的
}
}

}

public static void main(String[] args) throws Exception {
String s1 = nextString();
String s2 = nextString();
lcs(s1.toCharArray(),s2.toCharArray());
System.out.println(dp[s1.length()][s2.length()]);

closeAll();
}

public static void cinInit(){
cin.wordChars('a', 'z');
cin.wordChars('A', 'Z');
cin.wordChars(128 + 32, 255);
cin.whitespaceChars(0, ' ');
cin.commentChar('/');
cin.quoteChar('"');
cin.quoteChar('\'');
cin.parseNumbers();
}
public static int nextInt() throws Exception {
cin.nextToken();
return (int) cin.nval;
}
public static long nextLong() throws Exception {
cin.nextToken();
return (long) cin.nval;
}
public static double nextDouble() throws Exception {
cin.nextToken();
return cin.nval;
}
public static String nextString() throws Exception {
cin.nextToken();
return cin.sval;
}
public static void closeAll() throws Exception {
cout.close();
in.close();
out.close();
}
}

测试数据

输入:

1
2
3
abcfbc abfcab
programming contest
abcd mnp

输出:

1
2
3
4
2
0

求公共子序列

根据动态规划的状态,来判断我们要求得的序列中的字符有哪些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;


/**
* @Author DragonOne
* @Date 2021/12/5 21:27
* @墨水记忆 www.tothefor.com
*/
public class Main {
public static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
public static StreamTokenizer cin = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
public static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
public static Scanner sc = new Scanner(System.in);

public static int maxd = 10000+7;
public static int INF = 0x3f3f3f3f;
public static int mod = 998244353;
public static int[][] dp = new int[maxd][maxd]; //dp[i][j]表示串a的前i个和串b的前j个的公共子序列的最长长度

public static void lcs(char[] x,char[] y){
int lenx = x.length;
int leny = y.length;
for(int i=1;i<=lenx;++i){
for(int j=1;j<=leny;++j){
if(x[i-1]==y[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]); //当当前对应位置不等时,就看是不要串a的,还是不要串b中的
}
}
}
public static char[] LCSstr(char[] x,char[] y){
char[] ch = new char[maxd]; //用来存储公共子序列
int i = x.length;
int j = y.length;
int ind = 0; //记录公共子序列在ch数组中的下标,所以最后一定是等于公共子序列的长度的
while(i!=0 && j!=0){
if(x[i-1]==y[j-1]){
ch[ind++]=x[--i]; //因为字符数组是从0开始存储的,所以这里需要先减再存
j--;
}else if(dp[i][j-1]>dp[i-1][j]) { //因为前面lcs方法中,要的是dp较大值,所以要的是a串中的,b串中就可以少一个
j--;
}else if(dp[i][j-1]<=dp[i-1][j]){ //同理,这里要的是b串中的,所以让a串中少一个
i--;
}
}
return ch; //存储的最后结果是公共子序列的相反序列,因为是从后往前存的
}

public static void main(String[] args) throws Exception {
String s1 = nextString();
String s2 = nextString();
lcs(s1.toCharArray(),s2.toCharArray());

char[] ans = LCSstr(s1.toCharArray(),s2.toCharArray());
System.out.println(dp[s1.length()][s2.length()]);

int rlen = dp[s1.length()][s2.length()];
for(int i=rlen-1;i>=0;--i){
System.out.print(ans[i]);
}
System.out.println();

closeAll();
}

public static void cinInit(){
cin.wordChars('a', 'z');
cin.wordChars('A', 'Z');
cin.wordChars(128 + 32, 255);
cin.whitespaceChars(0, ' ');
cin.commentChar('/');
cin.quoteChar('"');
cin.quoteChar('\'');
cin.parseNumbers();
}
public static int nextInt() throws Exception {
cin.nextToken();
return (int) cin.nval;
}
public static long nextLong() throws Exception {
cin.nextToken();
return (long) cin.nval;
}
public static double nextDouble() throws Exception {
cin.nextToken();
return cin.nval;
}
public static String nextString() throws Exception {
cin.nextToken();
return cin.sval;
}
public static void closeAll() throws Exception {
cout.close();
in.close();
out.close();
}
}

测试数据

输入:

1
2
abcicba
abdkscab

输出:

1
2
4
abcb