问题描述
LCS问题可以分作最长公共子序列问题和最长公共子集问题,其区别在于前者要求的是连续的属于两个字符串序列的子集,而后者则不要求连续。如:
字符串一:桃李漫山过眼空。也宜恼损杜陵翁。若将玉骨冰姿比,李蔡为人在下中。
字符串二:过眼滔滔云共雾,算人间知己吾和汝。人有病,天知否?
一时书袋一时爽,一直吊一直爽!
公共子集
image.png公共子序列
image.png问题解析
公共子集
现有两个字符串str_1,str_2,str_1.subString[0,m+1]和str.subString[0,n+1]代表这两个字符串的前m项和前n项。LCS_1(String,String)方法返回两个字符串的最长公共子集。传参中两个字符串可以分立到两个互补的场景中考虑即两个字符串的尾项是否相等。
尾项相等
由公共子集的特性可知,相同的尾项一定会给最终的LCS结果作出+1的贡献,所以在这种情况下数学表达式为:
LCS_1(str_1.subString(0,m+1),str.subString(0,n+1))= LCS_1(str_1.subString(0,m),str.subString(0,n))+1
尾项不相等
尾项不相等时我们分别考虑两个字符串除去尾项的情况。所以表达式为:
LCS_1(str_1.subString(0,m+1),str.subString(0,n+1))= max(LCS_1(str_1.subString(0,m+1),str.subString(0,n)),LCS_1(str_1.subString(0,m),str.subString(0,n+1)))
最后加入初始条件和边界条件即构成了此题的动态规划算法。
公共子序列
本题需要构建两个字符串在每个位置上的重合矩阵。如"12345"和"34567":
00100
00010
00001
00000
00000
此时矩阵只保存了位置重合与否信息,我们对其进行优化,当str_2[m]
与str_2[n]
为相同字符时,arr[m][n]
的值为arr[m-1][n-1]+1
。此时矩阵变为:
00100
00020
00003
00000
00000
现在矩阵中已经包含有公共子串的长度信息,于是我们维护一个整型保存当前循环中的LCS长度并维护一个Stack<Integer>
(为了兼容同时存在多个LCS解的情况)以获得该子串尾项在矩阵中的坐标。到了这一步我们牺牲了O(nm)的空间复杂度换得了O(nm)的时间复杂度。但是仍有方法进一步减少内存空间的损耗。上述前提中我们已经通过开辟整型和栈在当前循环中保存了最长长度以及结果坐标,因此我们无需再维护一个完整而冗杂的矩阵,而只需要维护矩阵中当前循环中使用的每一行(即单行)即可。唯一需要注意的问题是算法要求保存前一行中arr[m-1][n-1]
的状态,因此我们通过逆序刷新的方式,如此一来在循环到arr[m]时,arr[m-1]保存的正是上一个循环中的字符重合状态。
不严谨代码
import java.util.Scanner;
import java.util.Stack;
/**
* 任意输入两个字符串,求出这两个字符串的公共子序列
* */
public class LCS1 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String x = sc.next();
String y = sc.next();
getLCS(x,y);
sc.close();
}
public static void getLCS(String x, String y){
char[] s1 = x.toCharArray();
char[] s2 = y.toCharArray();
int her = x.length();
int ver = y.length();
int[][] array = new int[her][ver];
for(int m = 0; m < her; m++){//filling the LCS matrix using dynamic planning
for(int n = 0; n < ver; n++){
if(s1[m] == s2[n]){
if(m==0 || n==0) array[m][n] = 1;//default case
else array[m][n] = array[m-1][n-1] + 1;
}else{
if(m==0 || n==0) array[m][n] = 0;//default case
else array[m][n] = max(array[m -1][n], array[m][n -1]);
}
}
}
// for(int m = 0; m < her; m++){//output the auxiliary matrix
// for(int n = 0; n < ver; n++){
// System.out.print(array[m][n]);
// }
// System.out.println();
// }
Stack<Character> stack = new Stack<>();
int i = x.length() - 1;
int j = y.length() - 1;
while((i >= 0) && (j >= 0)){//push result into stack
if(s1[i] == s2[j]){
stack.push(s1[i]);
i--;
j--;
}else{
if(i==0) j--;
else if(j==0) i--;
else if(array[i][j-1] > array[i-1][j]){
j--;
}
else{
i--;
}
}
}
while(!stack.isEmpty()){
System.out.print(stack.pop());
}
}
public static int max(int a, int b){
return (a > b)? a : b;
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
public class LCS2 {
public static List<String> findLongestCommonSet(String str1,String str2){
char[] chArr1 = str1.toCharArray();
int len1 = chArr1.length;
char[] chArr2 = str2.toCharArray();
int len2 = chArr2.length;
Stack<Integer> indexes = new Stack<>();
List<String> res = new ArrayList<String>();
int maxLen = 0;
int[] lenArr = new int[len1>len2?len1:len2];
for(int i=0;i<len1;i++) {
for(int j=len2-1;j>=0;j--) {//fill and flush the single array by reversed order
if(chArr1[i]==chArr2[j]) {
if(i==0 || j==0) lenArr[j] = 1;//default case
else lenArr[j] = lenArr[j-1]+1;
}
else lenArr[j]=0;
/*record every largest length
* with corresponding indexes of last char
*/
if(lenArr[j]>maxLen) {
indexes.clear();
maxLen = lenArr[j];
indexes.push(j);
}
else if(lenArr[j]==maxLen) indexes.push(j);
}
}
while(!indexes.isEmpty()) {
int term = indexes.pop();
res.add(str2.substring(term-maxLen+1,term+1));
}
System.out.println("Longest Common Sequence:"+maxLen);
return res;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String x = sc.next();
String y = sc.next();
List<String> list = findLongestCommonSet(x, y);
for (int i = 0; i < list.size(); i++) {
System.out.println("第" + (i + 1) + "个公共子串:" + list.get(i));
}
sc.close();
}
}
网友评论