class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> List[str]:
ans = []
n = len(s)
def backtrack(temp, start):
if start == n: ans.append(temp[1:])
for i in range(start, n):
if s[start:i + 1] in wordDict:
backtrack(temp + " " + s[start:i + 1], i + 1)
backtrack('', 0)
return ans
CPP Code:
class Solution {
int maxLen = 0;
unordered_set<string> ws;
vector<int> m;
vector<string> ans;
bool dfs(string &s, int i, string tmp) {
if (i == s.size()) {
ans.push_back(tmp);
return true;
}
if (m[i] == 0) return m[i];
m[i] = 0;
for (int j = min((int)s.size(), i + maxLen); j > i; --j) {
auto sub = s.substr(i, j - i);
if (ws.count(sub) && dfs(s, j, tmp.size() ? tmp + " " + sub : sub)) m[i] = 1;
}
return m[i];
}
public:
vector<string> wordBreak(string s, vector<string>& dict) {
ws = { dict.begin(), dict.end() };
for (auto &w : dict) maxLen = max(maxLen, (int)w.size());
m.assign(s.size(), -1); // -1 = unvisited, 0 = can not reach end, 1 = can reach end.
dfs(s, 0, "");
return ans;
}
};
因此一种优化思路就是将回溯的结果通过返回值的形式传递给父级函数,父级函数通过笛卡尔积构造 ans 即可。而这实际上和上面的解法复杂度是一样的, 但是经过这样的改造,我们就可以使用记忆化技巧减少重复计算了。因此理论上, 我们不存在回溯过程了。 因此时间复杂度就是所有的组合,即一次遍历以及内部的笛卡尔积,也就是 $O(N ^ 2)$。
代码
代码支持:Python3
Python3 Code:
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> List[str]:
n = len(s)
@lru_cache(None)
def backtrack(start):
ans = []
if start == n:
ans.append('')
for i in range(start, n):
if s[start:i + 1] in wordDict:
if start == 0: temp = s[start:i + 1]
else: temp = " " + s[start:i + 1]
ps = backtrack(i + 1)
for p in ps:
ans.append(temp + p)
return ans
return backtrack(0)
复杂度分析
令 C 为字典的总长度, N 为字典中最长的单词的长度。
时间复杂度:$O(N^2)$
空间复杂度:$O(C)$
这种记忆化递归的方式和 DP 思想一模一样, 大家可以将其改造为 DP,这个留给大家来完成。
更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 37K star 啦。