使用动态编程将字符串拆分为有效单词的字符串

Posted

技术标签:

【中文标题】使用动态编程将字符串拆分为有效单词的字符串【英文标题】: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否则。

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

【问题讨论】:

嗯,这是教科书上的练习。我没有练习的解决方案,也不知道如何解决这个问题。 首先想到的就是模棱两可。假设您的字典中有单词“was”、“her”和“washer”。不过,您可以只选择最短的单词。等等,不...您可以从单词中删除部分并使字符串无效-例如从“自动”中捕获“自动”。 您是否尝试先搜索答案?在 SO - ***.com/questions/4755157/split-string-into-words、***.com/questions/3553958/…、***.com/questions/3466972/… 上已经有几个关于这个问题的问题。其中一些提到了动态编程解决方案。 【参考方案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”都是可以在这个子字符串中找到的有效词。

以上是关于使用动态编程将字符串拆分为有效单词的字符串的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode No.139 单词拆分(动态规划)

Leetcode No.139 单词拆分(动态规划)

leetcode-139回溯超时动态规划单词拆分

将字符串拆分为单词并与其他数据重新连接

动态规划之字符串拆分

每日写题分享--单词拆分/动态规划/剪枝