web-dev-qa-db-ja.com

動的計画法を使用して、文字列を有効な単語の文字列に分割します

この問題を解決するには、動的計画法アルゴリズムを見つける必要があります。試しましたが、理解できませんでした。ここに問題があります:

N文字の文字列s [1 ... n]が与えられます。これは、すべての句読点が消えた(「itwasthebestoftimes ...」のように見える)破損したテキストドキュメントであると考えられます。辞書を使用してドキュメントを再構築したいとします。辞書はブール関数dict(*)の形式で利用でき、任意の文字列wに対して、wが有効なWordの場合はdict(w)の値が1になり、値が0になります。さもないと。

  1. 文字列s [*]を有効な単語のシーケンスとして再構成できるかどうかを決定する動的計画法アルゴリズムを提供します。 dictの各呼び出しに単位時間がかかると仮定すると、実行時間は最大でO(n ^ 2)である必要があります。
  2. 文字列が有効な場合は、アルゴリズムに対応する単語のシーケンスを出力させます。
20
Pet

圧縮されたドキュメントの長さを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は、アルゴリズムの最初の部分から生成されたブール配列です。

9
user97370

@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

6
mingxiao

O(N^2) Dpは明確ですが、辞書の単語を知っている場合は、いくつかの事前計算を使用して、O(N)でさらに高速にすることができると思います。 エイホ-コラシック

1
mariusgherman

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;
}
1
hidayat

以下は、この問題の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 << " ";
    }
    }
}
0
Pankaj Mistry

文字列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)です

これは教科書の練習のように見えるので、実際の分割位置を再構築するためのコードは記述しません。

0
Mahak Patidar