查找给定两个字符串

时间:2016-01-15 06:31:37

标签: java string algorithm

我遇到了一个问题陈述,找到给定的两个子字符串之间的所有公共子字符串,这样在每种情况下都必须打印最长的子字符串。问题陈述如下:

  

编写程序以查找两个给定字符串之间的公共子字符串。但是,不要包含较长公共子字符串中包含的子字符串。

     

例如,给定输入字符串eatsleepnightxyzeatsleepabcxyz,结果应为:

     
      
  • eatsleep(由于eatsleepnightxyz eatsleepabcxyz
  •   
  • xyz(由于eatsleepnightxyz eatsleepabcxyz
  •   
  • a(由于eatsleepnightxyz eatsleepabcxyz
  •   
  • t(由于eatsleepnightxyz eatsleepabcxyz
  •   
     

但是,结果集应包括e   eatsleepnightxyz eatsleepabcxyz,因为e已包含在上述eatsleep中。您也不应该包含eaeatats等,因为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

  1. S1的子串数明显为n1(n1 + 1)/ 2。
  2. 但是我们必须找到S1的子串的平均长度。
  3. 我们说它是m。我们将分别找到m。
  4. 时间复杂度检查m-String是否是一个子字符串 n-String是O(n * m)。
  5. 现在,我们正在检查每个m-String是S2的子串, 这是一个n2-String。
  6. 如上所述,这是一个O(n 2 m)算法。
  7. 整个算法所需的时间是
  8. Tn =(S1中的子串数)*(字符比较过程的平均子串长度时间)
  9. 通过进行某些计算,我得出结论 时间复杂度为O(n 3 m 2
  10. 现在,我们的工作是根据n1找到m。
  11. 尝试根据n1找到m。

    T n =(n)(1)+(n-1)(2)+(n-2)(3)+ ..... +(2)(n- 1)+(1)(n)
    其中T n 是所有子串的长度之和。

    平均值是该总和除以生成的子串总数。

    这只是一个求和和除法问题,其解决方案如下: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 个答案:

答案 0 :(得分:15)

使用适当的算法算法而不是蛮力方法会更好。维基百科描述了longest common substring problem的两种常见解决方案:

动态编程解决方案需要O( n m )时间和O( n m )空间。对于最长的公共子字符串,这几乎是对Wikipedia伪代码的直接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表示。

左侧,顶部以及16旁边的其他7怎么样?那些不算数,因为它们出现在由12345678对角线形成的矩形内 - 换句话说,它们已被eatsleep覆盖。

我建议除了构建表之外什么都不做。然后,进行第二次传递,从右下角向后迭代,以收集结果集。

答案 1 :(得分:5)

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