【编程题 字符串匹配 or 动态规划】HJ65 查找两个字符串a,b中的最长公共子串(详细注释 易懂)

题目描述:

题目链接:查找两个字符串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));
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值