【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;
        }

    }
}
posted @ 2023-07-19 15:16  万国码aaa  阅读(179)  评论(0编辑  收藏  举报