找出给定两个字符串的所有公共子串

30
我遇到了一个问题,需要找到给定两个子字符串之间的所有公共子串,并且在每种情况下都要打印最长的子串。问题陈述如下:
编写一个程序来查找两个给定字符串之间的公共子字符串。但是,请勿包括包含在较长公共子字符串中的子字符串。
例如,给定输入字符串eatsleepnightxyz和eatsleepabcxyz,则结果应为:
- eatsleep(由于eatsleepnightxyz和eatsleepabcxyz中的eatsleep) - xyz(由于eatsleepnightxyz和eatsleepabcxyz中的xyz) - a(由于eatsleepnightxyz和eatsleepabcxyz中的a) - t(由于eatsleepnightxyz和eatsleepabcxyz中的t)
但是,结果集不应包括eatsleepnightxyz中的e或者eatsleepabxyz中的e,因为这两个e已经包含在上面提到的eatsleep中。也不应包括ea、eat、ats等,因为这些也都被eatsleep覆盖了。
在此过程中,您无需使用String实用程序方法,如contains、indexOf、StringTokenizer、split和replace。
我的算法如下:我从暴力算法开始,并将在提高基本理解后切换到更优化的解决方案。
 For String S1:
     Find all the substrings of S1 of all the lengths
     While doing so: Check if it is also a substring of 
     S2.

尝试计算我的方法的时间复杂度。
让两个给定的字符串分别为n1-String和n2-String。
S1的子字符串数量显然是n1(n1+1)/2。
但我们需要找到S1子字符串的平均长度。
假设它是m。我们将单独找到m。
检查一个m-String是否是n-String的子字符串的时间复杂度是O(n*m)。
现在,我们正在检查每个m-String是否是n2-String中的子字符串。
这就是一个O(n^2 m)算法。
然后整个算法所需的时间为:
Tn=(S1中的子字符串数)*(平均子字符串长度*字符比较过程的时间)
通过进行某些计算,我得出结论时间复杂度是O(n^3 m^2)。
现在,我们的工作是以n1为单位找到m。
Tn = (n)(1) + (n-1)(2) + (n-2)(3) + ..... + (2)(n-1) + (1)(n)
其中Tn是所有子字符串长度的总和。
平均值将是此总和除以生成的子字符串的总数。
这只是一个求和和除法问题,其解决方案如下O(n)。
因此...
我的算法的运行时间为O(n^5)。
有了这个想法,我编写了以下代码:
 package pack.common.substrings;

 import java.util.ArrayList;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;

 public class FindCommon2 {
    public static final Set<String> commonSubstrings = new      LinkedHashSet<String>();

 public static void main(String[] args) {
    printCommonSubstrings("neerajisgreat", "neerajisnotgreat");
    System.out.println(commonSubstrings);
}

 public static void printCommonSubstrings(String s1, String s2) {
    for (int i = 0; i < s1.length();) {
        List<String> list = new ArrayList<String>();
        for (int j = i; j < s1.length(); j++) {
            String subStr = s1.substring(i, j + 1);
            if (isSubstring(subStr, s2)) {
                list.add(subStr);
            }
        }
        if (!list.isEmpty()) {
            String s = list.get(list.size() - 1);
            commonSubstrings.add(s);
            i += s.length();
        }
    }
 }

 public static boolean isSubstring(String s1, String s2) {
    boolean isSubstring = true;
    int strLen = s2.length();
    int strToCheckLen = s1.length();
    if (strToCheckLen > strLen) {
        isSubstring = false;
    } else {
        for (int i = 0; i <= (strLen - strToCheckLen); i++) {
            int index = i;
            int startingIndex = i;
            for (int j = 0; j < strToCheckLen; j++) {
                if (!(s1.charAt(j) == s2.charAt(index))) {
                    break;
                } else {
                    index++;
                }
            }
            if ((index - startingIndex) < strToCheckLen) {
                isSubstring = false;
            } else {
                isSubstring = true;
                break;
            }
        }
    }
    return isSubstring;
 }
}

我的代码解释:

 printCommonSubstrings: Finds all the substrings of S1 and 
                        checks if it is also a substring of 
                        S2.
 isSubstring : As the name suggests, it checks if the given string 
               is a substring of the other string.

问题:给定以下输入
  S1 = “neerajisgreat”;
  S2 = “neerajisnotgreat”
  S3 = “rajeatneerajisnotgreat”

如果是S1和S2,输出应该是:neerajisgreat。但如果是S1和S3,则输出应为:neerajisrajgreateat,但实际上我仍然得到的是neerajisgreat,我需要找出问题所在。
如何设计我的代码?
2个回答

25
你最好使用适当的算法来完成任务,而不是蛮力方法。维基百科描述了两种常见的解决最长公共子串问题的方法:
动态规划解决方案需要O(n m)时间和O(n m)空间。这基本上是维基百科最长公共子串伪代码的直接Java翻译。
public static Set<String> longestCommonSubstrings(String s, String t) {
    int[][] table = new int[s.length()][t.length()];
    int longest = 0;
    Set<String> result = new HashSet<>();

    for (int i = 0; i < s.length(); i++) {
        for (int j = 0; j < t.length(); j++) {
            if (s.charAt(i) != t.charAt(j)) {
                continue;
            }

            table[i][j] = (i == 0 || j == 0) ? 1
                                             : 1 + table[i - 1][j - 1];
            if (table[i][j] > longest) {
                longest = table[i][j];
                result.clear();
            }
            if (table[i][j] == longest) {
                result.add(s.substring(i - longest + 1, i + 1));
            }
        }
    }
    return result;
}

现在,您想要所有常见子字符串,而不仅仅是最长的。您可以增强此算法以包括更短的结果。让我们查看示例输入eatsleepnightxyzeatsleepabcxyz的表格:
  e a t s l e e p a b c x y z
e 1 0 0 0 0 1 1 0 0 0 0 0 0 0
a 0 2 0 0 0 0 0 0 1 0 0 0 0 0
t 0 0 3 0 0 0 0 0 0 0 0 0 0 0
s 0 0 0 4 0 0 0 0 0 0 0 0 0 0
l 0 0 0 0 5 0 0 0 0 0 0 0 0 0
e 1 0 0 0 0 6 1 0 0 0 0 0 0 0
e 1 0 0 0 0 1 7 0 0 0 0 0 0 0
p 0 0 0 0 0 0 0 8 0 0 0 0 0 0
n 0 0 0 0 0 0 0 0 0 0 0 0 0 0
i 0 0 0 0 0 0 0 0 0 0 0 0 0 0
g 0 0 0 0 0 0 0 0 0 0 0 0 0 0
h 0 0 0 0 0 0 0 0 0 0 0 0 0 0
t 0 0 1 0 0 0 0 0 0 0 0 0 0 0
x 0 0 0 0 0 0 0 0 0 0 0 1 0 0
y 0 0 0 0 0 0 0 0 0 0 0 0 2 0
z 0 0 0 0 0 0 0 0 0 0 0 0 0 3
  • eatsleep 的结果很明显:在左上角有一个 12345678 的对角线。
  • xyz 的结果是右下角的 123 对角线。
  • a 的结果由靠近顶部(第二行第九列)的 1 指示。
  • t 的结果由靠近左下角的 1 指示。

那么左侧、顶部以及与 67 相邻的其他 1 呢?这些不计入,因为它们出现在由 12345678 对角线形成的矩形内——换句话说,它们已经被 eatsleep 覆盖了。

我建议先进行一次构建表格的遍历。然后,进行第二次遍历,从右下角开始迭代,收集结果集。


让我们在聊天中继续这个讨论 - theimpatientcoder

5
通常这种子字符串匹配是通过一个名为Trie(发音为try)的单独数据结构来完成的。最适合此问题的特定变体是suffix tree。您的第一步应该是将输入构建成后缀树。然后,您需要使用后缀树来确定最长公共子字符串,这是一个很好的练习。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接