Solution 1
显然是对 0139. Word Break 的延申,有效性判定变成结果搜索,从头开始逐个单词的匹配。
难点:直接使用DFS会出现大量的重复计算,使用记忆化搜索改进
记忆化搜索:引入一个哈希表,保存索引 i i i向后之后缀的所有方案
边界判定:如果到达结尾,答案缺省为空串,使用空格拼接时用以判断
可以用的剪枝:如果未确定长度不满足任何一个单词,直接跳过(未实现)
- 时间复杂度: O ( n ⋅ 2 n ) O(n \cdot 2^n) O(n⋅2n),其中 n n n为输入字符串的长度。最坏情况下,每个位置都会划分考察并进行计算,每一个结果的子串需要 O ( n ) O(n) O(n)的生成时间
- 空间复杂度: O ( n ⋅ 2 n ) O(n \cdot 2^n) O(n⋅2n),其中 n n n为输入字符串的长度。对应地,最坏情况下需要同等规模的空间占用(每个子串方案都需要 O ( n ) O(n) O(n)的空间)
class Solution {
public:
vector<string> wordBreak(string s, vector<string>& wordDict) {
unordered_map<int, vector<string>> strDict;
auto wordSet = unordered_set(wordDict.begin(), wordDict.end());
this->search(s, 0, wordSet, strDict);
return strDict[0];
}
private:
void search(string s, int start, unordered_set<string>& wordSet, unordered_map<int, vector<string>>& strDict) {
// 记忆化搜索:如果当前子串已经完成了分析,直接返回
if (strDict.find(start) == strDict.end()) {
if (start == s.size()) {
// 结尾空串
strDict[start] = {""};
return;
}
strDict[start] = vector<string> ();
for (int i = start + 1; i <= s.size(); ++i) {
// 从start开始,枚举所有可能的第一个词
auto str = s.substr(start, i - start);
if (wordSet.find(str) != wordSet.end()) {
this->search(s, i, wordSet, strDict); // 继续向下递归,然后同样地调整strDict保存的内容
// 利用后缀更新当前strDict的内容
for (auto item: strDict[i]) {
if (!item.empty()) {
// 结尾空串
strDict[start].push_back(str + " " + item);
} else {
strDict[start].push_back(str);
}
}
}
}
}
}
};
Solution 2
Solution 1的Python实现
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> List[str]:
wordSet = set(wordDict)
strDict = dict()
def search(start: int) -> None:
# nonlocal wordSet
# nonlocal strDict
if start not in strDict:
if start == len(s):
strDict[start] = [""]
return
strDict[start] = list()
for i in range(start + 1, len(s) + 1):
word = s[start: i]
if word in wordSet:
search(i)
for item in strDict[i]:
if item == "":
strDict[start].append(word)
else:
strDict[start].append(word + " " + item)
search(0)
return strDict[0]