我遇到了一个问题陈述,找到给定的两个子字符串之间的所有公共子字符串,这样在每种情况下都必须打印最长的子字符串。问题陈述如下:
编写程序以查找两个给定字符串之间的公共子字符串。但是,不要包含较长公共子字符串中包含的子字符串。
例如,给定输入字符串
eatsleepnightxyz
和eatsleepabcxyz
,结果应为:
eatsleep
(由于eatsleepnightxyz
eatsleepabcxyz
)xyz
(由于eatsleepnightxyz
eatsleepabcxyz
)a
(由于eatsleepnightxyz
eatsleepabcxyz
)t
(由于eatsleepnightxyz
eatsleepabcxyz
)但是,结果集应不包括
e
eatsleepnightxyz
eatsleepabcxyz
,因为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
尝试根据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,输出应为:neerajis
和great
但在S1和S3的情况下,输出应该是:
neerajis
,raj
,great
,eat
但我仍然以neerajis
和great
作为输出。我需要弄清楚这一点。
我应该如何设计代码?
答案 0 :(得分:15)
使用适当的算法算法而不是蛮力方法会更好。维基百科描述了longest common substring problem的两种常见解决方案:suffix-tree和dynamic-programming。
动态编程解决方案需要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;
}
现在,您想要所有常见的子串,而不仅仅是最长的子串。您可以增强此算法以包含更短的结果。让我们检查表格中的示例输入eatsleepnightxyz
和eatsleepabcxyz
:
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
表示。左侧,顶部以及1
和6
旁边的其他7
怎么样?那些不算数,因为它们出现在由12345678
对角线形成的矩形内 - 换句话说,它们已被eatsleep
覆盖。
我建议除了构建表之外什么都不做。然后,进行第二次传递,从右下角向后迭代,以收集结果集。
答案 1 :(得分:5)
通常,这种类型的子字符串匹配是在称为Trie(发音为try)的单独数据结构的帮助下完成的。最适合此问题的特定变体是suffix tree。您的第一步应该是获取输入并构建后缀树。然后你需要使用后缀树来确定最长的公共子串,这是一个很好的练习。