zoukankan      html  css  js  c++  java
  • 最长公共子串(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)。

  • 相关阅读:
    hdu 5366 简单递推
    hdu 5365 判断正方形
    hdu 3635 并查集
    hdu 4497 数论
    hdu5419 Victor and Toys
    hdu5426 Rikka with Game
    poj2074 Line of Sight
    hdu5425 Rikka with Tree II
    hdu5424 Rikka with Graph II
    poj1009 Edge Detection
  • 原文地址:https://www.cnblogs.com/Skyar/p/5955013.html
Copyright © 2011-2022 走看看