需要建议提高分词速度(动态编程)

时间:2015-05-12 13:40:44

标签: c++ dynamic-programming word-break

问题是:给定一个字符串s和一个单词字典dict,确定s是否可以被分割成一个或多个字典单词的空格分隔序列。

例如,给定 s =" hithere", dict = [" hi","有"]。

返回true,因为" hithere"可以分段为" leet code"。

我的实施如下。此代码适用于正常情况。但是,它输入很多,如:

s =" aaaaaaaaaaaaaaaaaaaaaab",dict = {" aa"," aaaaaa"," aaaaaaaa"}。

我想记住已处理的子字符串,但是,我做不到。有关如何改进的任何建议?非常感谢!

class Solution {
public:
    bool wordBreak(string s, unordered_set<string>& wordDict) {
        int len = s.size();
        if(len<1) return true;
        for(int i(0); i<len; i++) {
            string tmp = s.substr(0, i+1);
            if((wordDict.find(tmp)!=wordDict.end()) 
               && (wordBreak(s.substr(i+1), wordDict)) )
                return true;
        }
        return false;
    }
};

4 个答案:

答案 0 :(得分:1)

这在逻辑上是一个两步过程。找到输入中的所有字典单词,考虑找到的位置(开始/结束对),然后查看这些单词是否涵盖整个输入。

所以你得到你的例子

aa:       {0,2}, {1,3}, {2,4}, ... {20,22}
aaaaaa:   {0,6}, {1,7}, ... {16,22}
aaaaaaaa: {0,8}, {1,9} ... {14,22}

这是一个图表,包含节点0-23和一堆边。但节点23 b完全无法访问 - 没有传入边缘。现在这是一个简单的图论问题

如果您的字典被组织为特里,那么找到字典单词出现的所有地方都非常容易。但由于std::map方法,即使equal_range也可以使用。你有一个O(N * N)嵌套循环用于开始和结束位置,每个单词都有O(log N)查找。但是,您可以快速确定s.substr(begin,end)是否仍然是一个可行的前缀,以及该前缀保留哪些字典单词。

另请注意,您可以懒惰地构建图形。盯着begin=0,您会找到边{0,2}, {0,6} and {0,8}。 (没有其他人)。您现在可以搜索节点2,6和8.您甚至可以使用一个好的算法 - A * - 建议您首先尝试节点8(仅在1个边缘可到达)。因此,您会找到节点{8,10}{8,14}{8,16}等。如您所见,您永远不需要构建包含{1,3}的图形部分它根本无法到达。

使用图论,很容易理解为什么你的蛮力方法会崩溃。您反复到达节点8(aaaaaaaa.aaaaaaaaaaaaaab),并且每次都从那里搜索子图。

进一步优化是运行双向A *。这将为您提供一个非常快速的解决方案。在第一步的后半部分,您将查找指向23, b的边。如果不存在,您立即知道节点{23}是孤立的。

答案 1 :(得分:0)

在您的代码中,您没有使用动态编程,因为您不记得已经解决过的子问题。

您可以启用此记忆,例如,通过将结果存储在原始字符串中的字符串s的起始位置,或甚至基于其长度(因为无论如何您正在使用的字符串是原始字符串的后缀,因此其长度唯一标识它)。然后,在wordBreak函数的开头,只检查是否已经处理了这样的长度,如果有,则不要重新运行计算,只需返回存储的值。否则,运行计算并存储结果。

另请注意,使用unordered_set的方法无法获得最快的解决方案。我能想到的最快的解决方案是O(N ^ 2),将所有单词存储在trie中(不在地图中!),并在沿着给定的字符串行走时跟随此trie。这实现了每循环迭代的O(1),不计算递归调用。

答案 2 :(得分:0)

尝试以下方法:

class Solution {
public:
    bool wordBreak(string s, unordered_set<string>& wordDict) 
    {
        for (auto w : wordDict)
        {
            auto pos = s.find(w);
            if (pos != string::npos)
            {
                if (wordBreak(s.substr(0, pos), wordDict) && 
                    wordBreak(s.substr(pos + w.size()), wordDict))
                    return true;
            }
        }
        return false;
    }
};

基本上你找到一个匹配项从输入字符串中删除匹配的部分,因此继续测试较小的输入。

答案 3 :(得分:0)

感谢所有评论。我将以前的解决方案改为下面的实现。在这一点上,我没有探索优化字典,但这些见解非常有价值,非常感谢。

对于目前的实施,您认为可以进一步改进吗?谢谢!

class Solution {
public:
    bool wordBreak(string s, unordered_set<string>& wordDict) {
        int len = s.size();
        if(len<1) return true;
        if(wordDict.size()==0) return false;

        vector<bool> dq (len+1,false);
        dq[0] = true;
        for(int i(0); i<len; i++) {// start point
            if(dq[i]) {
                for(int j(1); j<=len-i; j++) {// length of substring, 1:len
                    if(!dq[i+j]) {
                        auto pos = wordDict.find(s.substr(i, j));
                        dq[i+j] = dq[i+j] || (pos!=wordDict.end());
                    }
                }
            }
            if(dq[len]) return true;
        }
        return false;
    }
};