私は見つけるために問題文に出くわしました 指定された2つのサブストリング間のすべての共通サブストリング どのような場合でも、最も長い部分文字列を印刷する必要があります。問題のステートメントは次のとおりです。
与えられた2つの文字列間の共通部分文字列を見つけるプログラムを作成します。ただし、より長い共通部分文字列に含まれる部分文字列は含めないでください。
たとえば、入力文字列
eatsleepnightxyz
およびeatsleepabcxyz
を指定すると、結果は次のようになります。
eatsleep
(eatsleepnightxyz
eatsleepabcxyz
)xyz
(eatsleepnightxyz
eatsleepabcxyz
)a
(eatsleepnightxyz
eatsleepabcxyz
)t
(eatsleepnightxyz
eatsleepabcxyz
)ただし、結果セットはnot include
e
fromeatsleepnightxyz
eatsleepabcxyz
。これは、両方のe
sが上記のeatsleep
に既に含まれているためです。ea
、eat
、ats
なども含めないでください。これらもすべてeatsleep
でカバーされています。これでは、contains、indexOf、StringTokenizer、split、replaceなどのStringユーティリティメソッドを使用する必要はありません。
私のアルゴリズムは次のとおりです。私はブルートフォースから始めており、基本的な理解が向上したら、より最適化されたソリューションに切り替えます。
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.
私のアプローチの時間の複雑さを理解しようとします。
与えられた2つの文字列をn1-Stringとn2-Stringとする
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の場合、出力はneerajis
およびgreat
である必要がありますが、S1およびS3の場合、出力はneerajis
、raj
、great
、eat
ですが、それでもneerajis
およびgreat
を出力として取得しています。これを理解する必要があります。
コードをどのように設計すればよいですか?
総当たり的なアプローチよりも、タスクに適切なアルゴリズムを使用した方が良いでしょう。ウィキペディアでは、 最長共通部分文字列問題 : suffix-tree および dynamic-programming の2つの一般的なソリューションについて説明しています。
動的計画法の解決にはO(nm)時間とO(nmが必要です) スペース。これはほとんどの単純なJava最長共通部分文字列のWikipedia擬似コードの翻訳です。
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
の結果は、上部(2行9列)の近くの1
で示されます。t
の結果は、左下にある1
で示されます。1
および6
の左側、上部、および隣にある他の7
はどうですか?それらは12345678
対角線で形成された長方形内に表示されるため、カウントされません。つまり、すでにeatsleep
で覆われています。
テーブルを構築する以外に何もせずに1パスすることをお勧めします。次に、2番目のパスを作成し、右下から逆方向に繰り返して、結果セットを収集します。