• 最长公共子串(LCS:Longest Common Substring)


    最长公共子串(LCS:Longest Common Substring)是一个非常经典的面试题目,本人在乐视二面中被面试官问过,惨败在该题目中。

    什么是最长公共子串

    最长公共子串问题的基本表述为:给定两个字符串,求出它们之间最长的相同子字符串的长度。

    最直接的解法就是暴力解法:遍历所有子字符串,比较它们是否相同,然后去的相同子串中最长的那个。对于长度为n的字符串,它子串的数量为n(n-1)/2,假如两个字符串长度均为n,那么该解法的复杂度为O(n^4),想想并不是取出所有的子串,那么该解法的复杂度为O(n^3)。

    复杂度太高,可以进行优化,可以利用动态规划法(有重叠的子问题)。

    暴力解法

    对于该问题,直接的思路就是要什么就找什么,要子串就要子串,要相同就比较每个字符,要长度就计算长度,所以很容易写出下列代码:

     1 //暴力
     2     public static int longestCommonSubstring(String s1, String s2){
     3         char[] str1 = s1.toCharArray();
     4         char[] str2 = s2.toCharArray();
     5         int str1_length = str1.length;
     6         int str2_length = str2.length;
     7         if(str1_length == 0 || str2_length == 0)
     8             return 0;
     9         //最大长度
    10         int maxLength = 0;
    11         int compareNum = 0;
    12         int start1 = -1;
    13         int start2 = -1;
    14         for(int i=0;i<str1_length;i++){
    15             for(int j=0;j<str2_length;j++){
    16                 int m = i;
    17                 int n = j;
    18                 //相同子串长度
    19                 int length = 0;
    20                 while(m < str1_length && n < str2_length){
    21                     compareNum++;
    22                     if(str1[m] != str2[n])
    23                         break;
    24                     m++;
    25                     n++;
    26                     length++;
    27                 }
    28                 if(length > maxLength){
    29                     maxLength = length;
    30                     start1 = i + 1;
    31                     start2 = j + 1;
    32                 }
    33                 
    34             }
    35         }
    36         System.out.println("比较次数" + compareNum + ",s1起始位置:" + start1 + ",s2起始位置:" + start2);        
    37         return maxLength;
    38     }

    该思路以字符串中每个字符作为子串的开始,判断以此开始的子串的相同字符所能达到的最大长度。从上述代码来看,复杂度是O(n^2),但是在比较两个相同开端的子串的效率不是O(1),是O(n),所以上述算法的复杂度为O(n^3)。

    动态规划-空间换时间

    上述解法回答面试官,面试官肯定会让你优化!

    我们发现,在相同开端的子串的比较中,有很多事重复动作。比如在比较以i,j分别为起点的子串时,有可能会进行i+1和j+1以及i+2和j+2位置的字符的比较。而以i+1,j+1分别为起点的子串时,这些字符又被比较了一次。也就说该问题有非常相似的子问题,而子问题之间又有重叠,这就给动态规划法创造了契机。

    暴力解法是以子串开端开始寻找,现在换个思路,以相同子串的字符结尾来利用动态规划法。

    假设两个字符串分别为A、B,A[i]和B[j]分别表示其第i和j个字符,再假设K[i,j]表示以A[i]和B[j]结尾的子串的最大长度。那么A,B分别再向下走一个字符,我们可以推断出K[i+1,j+1]与K[i,j]之间的关系,如果A[i] == B[j],那么K[i+1,j+1] = K[i,j] + 1;否则K[i+1,j+1] =0。而如果A[i+1]和B[j+1]相同,那么就只要在以A[i]和B[j]结尾的最长相同子串之后分别添上这两个字符即可,这样就可以让长度增加一位,综上所述,就是K[i+1,j+1] = (A[i] == B[j] ? K[i,j] + 1 : 0)的关系。

    由上述K[i+1,j+1] = (A[i] == B[j] ? K[i,j] + 1 : 0)的关系,想到了使用二维数组来存储两个字符串之间的相同子串关系,因为K[i+1,j+1] = (A[i+1] == B[j+1] ? K[i,j] + 1 : 0)关系,只计算二维数据的最上列和最左列数值即可,其他数值通过K[i+1,j+1] = (A[i+1] == B[j+1] ? K[i,j] + 1 : 0)可得。如下图所示:

     代码如下:

     1 //优化
     2     public static int longestCommonSubstring1(String s1, String s2){
     3         if(s1.length() == 0 || s2.length() == 0)
     4             return 0;
     5         char[] str1 = s1.toCharArray();
     6         char[] str2 = s2.toCharArray();
     7         int start1 = -1;
     8         int start2 = -1;
     9         int[][] results = new int[str2.length][str1.length];
    10         //最大长度
    11         int maxLength = 0;
    12         int compareNum = 0;
    13         for(int i=0;i<str1.length;i++){
    14             results[0][i] = (str2[0] == str1[i] ? 1 : 0);
    15             compareNum++;
    16             for(int j=1;j<str2.length;j++){
    17                 results[j][0] = (str1[0] == str2[j] ? 1 : 0);
    18                 if(i>0 && j>0){
    19                     if(str1[i] == str2[j]){
    20                         results[j][i] = results[j-1][i-1] + 1;
    21                         compareNum++;
    22                     }
    23                 }
    24                 if(maxLength < results[j][i]){
    25                     maxLength = results[j][i];
    26                     start1 = i - maxLength + 2;
    27                     start2 = j - maxLength + 2;
    28                 }
    29             }
    30         }
    31         System.out.println("比较次数" + (compareNum+str2.length) + ",s1起始位置:" + start1 + ",s2起始位置:" + start2);
    32         return maxLength;
    33     }

    用二维数组保存计算结果,避免了重复计算,运算的时间复杂度降低到了O(n^2)。

  • 相关阅读:
    htop命令使用详解
    三月江南
    linux下文本三剑客之sed
    linux下五种查找命令
    Spring MVC 全局异常处理&文件上传
    About Spring MVC
    JSP
    java多线程
    Java中map接口 遍历map
    log4j
  • 原文地址:https://www.cnblogs.com/Skyar/p/5955013.html
Copyright © 2020-2023  润新知