题目描述:
题目链接:查找两个字符串a,b中的最长公共子串_牛客题霸_牛客网 (nowcoder.com)
查找两个字符串a,b中的最长公共子串。若有多个,输出在较短串中最先出现的那个。
注:子串的定义:将一个字符串删去前缀和后缀(也可以不删)形成的字符串。请和“子序列”的概念分开!
数据范围:字符串长度1 <= length <=300 1≤length≤300
进阶:时间复杂度:O(n^3) ,空间复杂度:O(n)
输入描述:
输入两个字符串
输出描述:
返回重复出现的字符
示例1
输入:
abcdefghijklmnop abcsafjklmnopqrstuvw 输出:jklmnop
题目理解:
要求从一个字符串中找出另一个字符串中也存在的最长子串,子串是连续的。 题意好理解,也容易形成大致思路,但真正动起手做它的时候,还是挺麻烦的。
思路讲解:
字符串匹配思路:
举例说明,A字符串为 abcdefghijklmnop(短字符串) ,B字符串(长字符串)为 abcsafjklmnopqrstuvw,首先设三个指针,其中两个指针指向 短字符串(两个指针设为 i 和 k),一个指针指向长字符串(指针设为 j),一开始 i 和 k 都指向起始位置, 指针 j 每次都遍历字符串B,每当它找到B字符串中与A字符串 i 所指字符相同时,i 和 j 就同时往前遍历,当不相同时 k 到 i 就是两个字符串相同的长度 ,保存这个长度的字符串,作为当前最新的最长公共子串。
然后 i 回到 k 所在的位置, j 继续向前遍历,直到把B字符串遍历完成,那么本次遍历结束。然后 k+1 ,i 回到 k所在的位置,j 回到0 位置,进行下一次遍历,然后更新当前的最长公共子串,如果小于等于就不更新。
上面为什么 i 和 j 同时遍历结束后,i 要回到 k所在位置,j 要继续往前遍历呢? 因为要应对以下情况,当A字符串是 abbc ,而B字符串是 abbceabbc ,对于这种情况,如果i没有回到k位置,j没有继续往前遍历, 那么最终结果就是 abb ,就错了。
这种思路能看得出来,时间复杂度是 N的三次方 ,是符合题目进阶要求的。
动态规划思路:
设一个二维数组,记录两个字符串的比较结果,比较之前要先给两个字符串前面加一个空格,可以看下图理解。 通过这种方式比较,如果相等,就看它们的前一个(也就是看 [ i -1 ][ j -1] ),在前一个的基础上,加 1 ,得到最终结果。每次相等,加1 后,就和最大值比较(初始值为 0 ),每当大于最大值,就记下 短字符串的下标(也就是 i , 为什么不是 i-1 ,因为字符串截取函数是左闭右开,所以不能减 1),然后利用 下标截取就好了。 要注意上面记录的下标,是两个字符串最长公共子串的最后一个字符下标+1 ,所以是用它减 max ,而不是加 max 。
字符串匹配代码注释:
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
String str1 = scan.next();
String str2 = scan.next();
// 如上思路讲解的,始终保证A字符串是短字符串,B字符串是长字符串,因为题目要求
// 是从短字符串中或者最长公共子串
if (str1.length() > str2.length()) {
String tmp = str2;
str2 = str1;
str1 = tmp;
}
//用一个 顺序表 记录每次截取的字符串
ArrayList<String> list = new ArrayList<>();
// 用一个空字符串 占位
String nullStr = "";
list.add(nullStr);
int king = 0;
while (king < str1.length() ) {
// 这里的 first 指针就是上面思路讲解中提到的 i 指针,king指针就是上面提到的 k 指针,
// 每次遍历 first 指针都要回到 king 指针
int first = king;
// second 指针就是上面提到的 j 指针,它用来遍历长字符串,每次循环它都要把长字符串走完
int second = 0;
while (first < str1.length() && second < str2.length()) {
first = king;
if (str1.charAt(first) == str2.charAt(second)) {
// 如果两个字符相等,就从这里开始,一起往前遍历 ,但同时不能越界
while(first< str1.length() && second < str2.length()&&
str1.charAt(first) == str2.charAt(second)){
first++;
second++;
}
if(list.get(0).length() < str1.substring(king,first).length()){
// 如果顺便表中 原有的字符串长度小于现在的字符串长度 ,就删掉原有的,放入
// 当下的更长的字符串
list.remove(list.get(0));
String temp = str1.substring(king,first);
list.add(temp);
}
}
second++;
}
king++;
}
System.out.println(list.get(0));
}
}
动态规划代码注释:
import java.util.*;
public class Main{
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
String str1 = scan.next();
String str2 = scan.next();
// 始终保证 第一个字符串是短字符串,第二个是长字符串
if (str1.length() > str2.length()) {
String tmp = str2;
str2 = str1;
str1 = tmp;
}
int m = str1.length();
int n = str2.length();
int max =0;
int index =0;
// 设一个整数二维数组,默认全为0
int[][] flag = new int[m+1][n+1];
for(int i=1;i<= m;i++){
for(int j=1;j<= n;j++){
if(str1.charAt(i-1)== str2.charAt(j-1)){
flag[i][j] = flag[i-1][j-1]+1;
if(flag[i][j]>max){
max = flag[i][j];
index = i;
}
}
}
}
// 由于截取函数是左闭右开,最后截取 index - max 到index 这一段就好了
System.out.println(str1.substring(index-max,index));
}
}