【Java】最长公共子串问题
1-问题定义
2-算法问题描述
类型一: 给定两个字符串str1和str2,输出两个字符串的最长公共子串,如果最长公共子串为空,输出-1;
类型二: 给定两个字符串str1和str2及其对应长度, 求出两串的最长公共子串的长度。
此处将两种类型的题目杂糅:给定两个字符串str1和str2及其对应长度,输出两个字符串的最长公共子串及其长度。
方法1-分别滑动比对
解决思路:
想象两个字符串一上一下放置, 下面一串尾部对齐上面一串头部,开始向右滑动,动一次对比一遍对齐部分, 直到两串的头部碰头,再变成上面一串向左滑动...比对过程中记录公共子串的长度和末尾字符的位置。
代码实现:
import java.util.Scanner;
/*
最长公共子串
输入两个字符串和对应长度
输出最长公共子串和其长度
暴力解决方法(滑动比对,空间复杂度O(1))
*/
public class Test03 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String str1 = sc.next();
int m = sc.nextInt();
String str2 = sc.next();
int n = sc.nextInt();
System.out.println(findLongest(str1,m,str2,n));
}
}
public static String findLongest(String str1, int m, String str2, int n){
int max = 0;
//想象两个字符串一上一下,先下面一串从上面一串头部开始向右滑动对比,直到两串头部碰头变成上面一串向左滑动...
int up = 0;//上面一串对比起始位置
int down = n-1;//下面一串对比起始位置
int end = 0;//最长公共子串尾部在原串的位置记录
while(up < m){
int i = up;
int j = down;
int len = 0;//目前公共子串长度
//开始比对
while(i < m && j < n){
if(str1.charAt(i) == str2.charAt(j)){
len++;
}
if(len > max){
max = len;
end = i;
}
i++;
j++;
}
//一轮对比后,判断上下哪个串需要滑动,并执行
if(down > 0){
down--;
}else{
up++;
}
}
//判断最长子串如果为空,输出-1
if(max == 0) return "-1";
return str1.substring(end-max+1, end+1) + ", " + max;
}
}
方法2-动态规划
解决思路:
用矩阵d[m][n]表示两个字符串(长度分别为m,n)的各元素之间的关系,矩阵的行号i和列号j分别对应两个字符串的i和j位置,如果对应元素不等则d[i][j]为0,表示此处没有公共子串,如果相等,则此处有公共子串,d[i][j]则记录到此为止公共子串的长度,所以需要访问d[i-1][j-1]的值,即回溯两个字符串,此时d[i][j]=d[i-1][j-1]+1 有些视频讲得比较清楚,这里是视频解析链接
代码实现:
import java.util.Scanner;
/*
最长公共子串
输入两个字符串和对应长度
输出最长公共子串和其长度
动态规划方法
*/
public class Test02 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()){
String str1 = sc.next();
int m = sc.nextInt();
String str2 = sc.next();
int n = sc.nextInt();
System.out.println("最长公共子串及其长度分别为: "+findLongest(str1,m,str2,n));
}
}
public static String findLongest(String str1, int m, String str2, int n){
char[] ch1 = str1.toCharArray();
char[] ch2 = str2.toCharArray();
int[][] dp = new int[m][n];
int max = 0;
int end = 0;
//先处理边缘
for(int i = 0;i < ch1.length; i++){
if (ch1[i] == ch2[0]) {
dp[i][0] = 1;
}
}
for(int j = 0;j < ch2.length; j++){
if (ch2[j] == ch1[0]) {
dp[0][j] = 1;
}
}
//再处理内部,省得每次判断是否有前面一个字符
for(int i = 1;i < ch1.length; i++){
for(int j = 1;j < ch2.length; j++){
if(ch1[i] == ch2[j]){
dp[i][j] = dp[i-1][j-1] + 1;
}
if(dp[i][j] > max){
max = dp[i][j];
end = i;
}
}
}
if(max == 0){
return "-1";
}else{
return str1.substring(end-max+1,end+1)+", "+ max;
}
}
}