题目
Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
For example, given
s = "leetcode"
,
dict = ["leet", "code"]
.
Return true because "leetcode"
can be segmented as "leet code"
.
思路1
写的第一个版本是超时的,但是思路应当是正确的,反正就是动态规划,但我用的是递归方法。
就是如果找到s[start,end]是单词表中的单词时,有两种处理,一种是算入然后往后走;另一种是忽略这次匹配成功,继续往后寻找。
代码大概这样:
class Solution {
public:
int findNextWord(const string& s, int start, int curr, unordered_set<string>& wordDict) {
//Find the end position of next possible word, or return -1
string word = s.substr(start, curr - start + 1);
for (int i = curr + 1; i < s.size(); i++) {
word += s[i];
if (wordDict.count(word) > 0) {
return i;
}
}
return -1;
}
bool parseSeq(const string& s, unordered_set<string>& wordDict, int start, int end, map<int, bool>& cache) {
//Judge s[start...end]
if (end >= s.size())
return false;
if (cache.count(start) > 1)
return cache[start];
string word = s.substr(start, end - start + 1);
if (wordDict.count(word) > 0) {
bool withThisWord = parseSeq(s, wordDict, end + 1, end + 1, cache);
if (end + 1 == s.size() || withThisWord) {
cache[start] = true;
return true;
}
}
int nextPos = findNextWord(s, start, end, wordDict);
if (-1 != nextPos) {
bool appendWord = parseSeq(s, wordDict, start, nextPos, cache);
if (appendWord) {
cache[start] = true;
return true;
}
}
cache[start] = false;
return false;
}
bool wordBreak(string s, unordered_set<string>& wordDict) {
map<int, bool> cache;
return parseSeq(s, wordDict, 0, 0, cache);
}
};
但是如果这里出现了超级多的单字,比如字符串是
aaaaaaaaaaaaaaaaaaaaaaa
然后字典是
{a,aa,aaa,aaaa}
这就搞笑了,得匹配死,所以肯定是超时。
思路2
这个是讨论组里看来的,突然发现一般来说DP递归能做的,就可以从后往前用非递归的方法写。对于这个问题来说,从后往前并不像做LCS那样时要完全倒着写。举个例子,有字符串str=thisisawesome 和字典{this, is, a, we, some, awesome} 的时候,可以用一个缓存cache来记录是否在str[i]位置有单词结束,比如str[3]位置就是”this”这个词结束的地方。当顺着继续搜索的时候,往前查找缓存记录,可以知道如果cache[i]=true的话,那才有可能从i这个位置往后到当前位置组成新单词,接着去查找字典看看有没有这个单词,如果没有的话,就得继续往前找到新的可用的开头i。这里加黑的“才有可能”是比较关键的。
代码大概是这样
bool wordBreak(string s, unordered_set<string>& wordDict) {
if (wordDict.empty())
return false;
vector<bool> cache(s.size() + 1, false); //If a word ends at i, cache[i] = true
cache[0] = true;
for (int i = 0; i <= s.size(); i++) {
for (int j = i - 1; j >= 0; j--) {
if (cache[j]) {
//Look backward and start at the end place of previous word
string word = s.substr(j, i - j); // s[j...i-1]
if (wordDict.find(word) != wordDict.end()) {
cache[i] = true;
break;
}
}
}
}
return cache[s.size()];
}
这里面向前迭代不断寻找cache[i]并尝试的情况,对应于之前思路1里面找到可用单词后的决定是否使用的分支情况。但是由于减少了一大堆递归调用,所以实际情况下的复杂度降低了不少。