【问题标题】:Split a string to a string of valid words using Dynamic Programming使用动态编程将字符串拆分为有效单词的字符串
【发布时间】:2011-07-15 17:01:03
【问题描述】:

我需要找到一个动态规划算法来解决这个问题。我试过但无法弄清楚。问题来了:

给你一个包含 n 个字符的字符串 s[1...n],你认为它是一个损坏的文本文档,其中所有标点符号都消失了(因此它看起来像“itwasthebestoftimes...”)。您希望使用字典重建文档,该字典以布尔函数 dict(*) 的形式提供,这样对于任何字符串 w,如果 w 是有效单词,dict(w) 的值为 1,并且值为 0否则。

  1. 给出一个动态规划算法来确定字符串 s[*] 是否可以被重构为一个有效的单词序列。假设每次调用dict都需要单位时间,运行时间最多应该是O(n^2)。
  2. 如果字符串有效,则让您的算法输出相应的单词序列。

【问题讨论】:

标签: algorithm dynamic big-o text-segmentation


【解决方案1】:

让你的压缩文档的长度为 N。

令 b(n) 为布尔值:如果文档可以从文档中的位置 n 开始拆分为单词,则为 true。

b(N) 为真(因为空字符串可以拆分为 0 个单词)。 给定 b(N), b(N - 1), ... b(N - k),您可以通过考虑所有以字符 N - k - 1 开头的单词来构造 b(N - k - 1)。如果有任何这样的词 w,设置 b(N - k - 1 + len(w)),然后设置 b(N - k - 1) 为真。如果没有这个词,则将 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 是从算法的第一部分生成的布尔数组。

【讨论】:

  • 如果考虑所有以字符 N - k - 1 开头的单词,是不是效率低下。更好的方法是b[i] = true if there exists i <= j < N such that dict(s[i..j]) and b[j+1..N-1]
【解决方案2】:

将@MinhPham 的建议正式化。

这是一个动态编程解决方案。

给定一个字符串str,让

b[i] = true 如果子字符串 str[0...i](包括)可以拆分成有效的单词。

在 str 中添加一些起始字符,例如 !,以表示空字。 str = "!" + 字符串

基本情况是空字符串,所以

b[0] = 真。

对于迭代情况:

b[j] = true 如果 b[i] == true 并且 str[i..j] 是所有 i 的词

【讨论】:

    【解决方案3】:

    O(N^2) Dp 很清楚,但如果您知道字典中的单词,我认为您可以使用一些预计算在O(N) 中更快地获得它。 Aho-Corasick

    【讨论】:

      【解决方案4】:

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

      【讨论】:

      • 你为什么不描述你做了什么并解释你为什么这样做?
      • @tobias 你能解释一下它的算法吗
      • 只是一些随机代码对任何人都没有帮助。您应该提供解释。
      【解决方案5】:

      字符串 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)

      由于这看起来像教科书练习,我不会编写代码来重建实际的分割位置。

      【讨论】:

        【解决方案6】:

        下面是这个问题的 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 << " ";
            }
            }
        }
        

        【讨论】:

        • 您的字典没有包含字符串中所有可能的单词。例如“a”、“as”和“he”都是可以在这个子字符串中找到的有效词。
        猜你喜欢
        • 2017-03-17
        • 2011-05-20
        • 2011-06-12
        • 2014-06-09
        • 2011-09-11
        • 1970-01-01
        • 2022-01-18
        • 2011-10-23
        • 2011-11-03
        相关资源
        最近更新 更多