この問題を解決するには、動的計画法アルゴリズムを見つける必要があります。試しましたが、理解できませんでした。ここに問題があります:
N文字の文字列s [1 ... n]が与えられます。これは、すべての句読点が消えた(「itwasthebestoftimes ...」のように見える)破損したテキストドキュメントであると考えられます。辞書を使用してドキュメントを再構築したいとします。辞書はブール関数dict(*)の形式で利用でき、任意の文字列wに対して、wが有効なWordの場合はdict(w)の値が1になり、値が0になります。さもないと。
圧縮されたドキュメントの長さをNとします。
b(n)をブール値とします:ドキュメントをドキュメント内の位置nから始まる単語に分割できる場合はtrue。
b(N)はtrueです(空の文字列は0ワードに分割できるため)。 b(N)、b(N --1)、... b(N --k)が与えられると、文字N --k -1で始まるすべての単語を考慮してb(N --k -1)を構築できます。 b(N --k --1 + len(w))が設定されたそのような単語wは、b(N --k -1)をtrueに設定します。そのような単語がない場合は、b(N --k --1)をfalseに設定します。
最終的に、b(0)を計算します。これは、ドキュメント全体を単語に分割できるかどうかを示します。
擬似コードの場合:
def try_to_split(doc):
N = len(doc)
b = [False] * (N + 1)
b[N] = True
for i in range(N - 1, -1, -1):
for Word starting at position i:
if b[i + len(Word)]:
b[i] = True
break
return b
「位置iから始まる単語」を効率的にするためにできるいくつかのトリックがありますが、O(N ^ 2)アルゴリズムが必要なので、辞書でiから始まるすべての文字列を検索できます。
単語を生成するには、上記のアルゴリズムを変更して適切な単語を保存するか、次のように生成します。
def generate_words(doc, b, idx=0):
length = 1
while true:
assert b(idx)
if idx == len(doc): return
Word = doc[idx: idx + length]
if Word in dictionary and b(idx + length):
output(Word)
idx += length
length = 1
ここで、bは、アルゴリズムの最初の部分から生成されたブール配列です。
@MinhPhamが提案したことを形式化するため。
これは動的プログラミングソリューションです。
文字列strが与えられた場合、
b [i] =部分文字列str [0 ... i](両端を含む)を有効な単語に分割できる場合はtrue。
空の単語を表すために、strの前に開始文字、たとえば!を付けます。 str = "!" + str
基本ケースは空の文字列なので、
b [0] = true。
反復の場合:
b [i] == trueで、str [i..j]がすべてのi <jの単語である場合、b [j] = true
O(N^2)
Dpは明確ですが、辞書の単語を知っている場合は、いくつかの事前計算を使用して、O(N)
でさらに高速にすることができると思います。 エイホ-コラシック
C++のdpソリューション:
int main()
{
set<string> dict;
dict.insert("12");
dict.insert("123");
dict.insert("234");
dict.insert("12345");
dict.insert("456");
dict.insert("1234");
dict.insert("567");
dict.insert("123342");
dict.insert("42");
dict.insert("245436564");
dict.insert("12334");
string str = "123456712334245436564";
int size = str.size();
vector<int> dp(size+1, -1);
dp[0] = 0;
vector<string > res(size+1);
for(int i = 0; i < size; ++i)
{
if(dp[i] != -1)
{
for(int j = i+1; j <= size; ++j)
{
const int len = j-i;
string substr = str.substr(i, len);
if(dict.find(substr) != dict.end())
{
string space = i?" ":"";
res[i+len] = res[i] + space + substr;
dp[i+len] = dp[i]+1;
}
}
}
}
cout << *dp.rbegin() << endl;
cout << *res.rbegin() << endl;
return 0;
}
以下は、この問題のO(n ^ 2)ソリューションです。
void findstringvalid() {
string s = "itwasthebestoftimes";
set<string> dict;
dict.insert("it");
dict.insert("was");
dict.insert("the");
dict.insert("best");
dict.insert("of");
dict.insert("times");
vector<bool> b(s.size() + 1, false);
vector<int> spacepos(s.size(), -1);
//Initialization phase
b[0] = true; //String of size 0 is always a valid string
for (int i = 1; i <= s.size(); i++) {
for (int j = 0; j <i; j++) {
//string of size s[ j... i]
if (!b[i]) {
if (b[j]) {
//check if string "j to i" is in dictionary
string temp = s.substr(j, i - j);
set<string>::iterator it = dict.find(temp);
if (it != dict.end()) {
b[i] = true;
spacepos[i-1] = j;
}
}
}
}
}
if(b[s.size()])
for (int i = 1; i < spacepos.size(); i++) {
if (spacepos[i] != -1) {
string temp = s.substr(spacepos[i], i - spacepos[i] + 1);
cout << temp << " ";
}
}
}
文字列s []は、複数の方法に分割される可能性があります。以下の方法では、s []を分割できる単語の最大数を見つけます。以下は、アルゴリズムのスケッチ/擬似コードです
bestScore [i]->最初のi文字を分割できる単語の最大数を格納します(それ以外の場合はMINUS_INFINITYになります)
for (i = 1 to n){
bestScore[i] = MINUS_INFINITY
for (k = 1 to i-1){
bestScore[i] = Max(bestSCore[i], bestScore[i-k]+ f(i,k))
}
}
ここで、f(i、k)は次のように定義されます。
f(i,k) = 1 : if s[i-k+1 to i] is in dictionary
= MINUS_INFINITY : otherwise
bestScore [n]は、s []を分割できる単語の最大数を格納します(値がMINUS_INFINIYの場合、s []は分割できません)
明らかに実行時間はO(n ^ 2)です
これは教科書の練習のように見えるので、実際の分割位置を再構築するためのコードは記述しません。