题目描述
给定一个非空字符串 s
和一个非空词典 wordDict
,找到 s
被分割成一个或多个单词分隔序列的所有方案。
样例
输入:
s = "catsanddog"
wordDict = ["cat", "cats", "and", "sand", "dog"]
输出:
[
"cats and dog",
"cat sand dog"
]
输入:
s = "pineapplepenapple"
wordDict = ["apple", "pen", "applepen", "pine", "pineapple"]
输出:
[
"pine apple pen apple",
"pineapple pen apple",
"pine applepen apple"
]
输入:
s = "catsandog"
wordDict = ["cats", "dog", "sand", "and", "cat"]
输出:
[]
限制
- 词典中的词可以用多次
- 词典中没有重复的单词。
算法
(动态规划 + 递归枚举)
- 按照 Word Break 的动态规划思路,求出 $f$ 数组,$f(i)$ 表示
[0, i]
是否可以被完整分割。 - 根据 $f$ 数组,从位置
n
开始进行递归枚举所有可能分割的方式。枚举时,只需要判断 $f(i)$ 是否可能从 $f(j)$ 转移过来的,若可能,则递归到位置 $j$。
时间复杂度
- 动态规划的时间复杂度为 $O(n^3)$。
- 递归枚举的时间复杂度与方案数相等,为指数级。
C++ 代码
class Solution {
public:
void dfs(int i, const vector<bool>& f, const string &s,
const unordered_set<string>& wordSet,
vector<string>& cur_string, vector<string>& ans) {
if (i == 0) {
string tmp = "";
for (int k = cur_string.size() - 1; k >= 0; k--) {
tmp += cur_string[k];
if (k > 0)
tmp += " ";
}
ans.push_back(tmp);
return;
}
for (int j = 0; j < i; j++) {
string cur = s.substr(j, i - j);
if (f[j] && wordSet.find(cur) != wordSet.end()) {
cur_string.push_back(cur);
dfs(j, f, s, wordSet, cur_string, ans);
cur_string.pop_back();
}
}
}
vector<string> wordBreak(string s, vector<string>& wordDict) {
int n = s.length();
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<bool> f(n + 1, false); // 为了方便,f 数组的下标从 1 开始。
f[0] = true;
for (int i = 1; i <= n; i++)
for (int j = 0; j < i; j++) {
string cur = s.substr(j, i - j);
if (wordSet.find(cur) != wordSet.end())
f[i] = f[i] | f[j];
}
vector<string> ans;
vector<string> cur_string;
dfs(n, f, s, wordSet, cur_string, ans);
return ans;
}
};
全和y总反着来hh
吐了 这题真长
时间复杂度n^2 吧
这里动态规划的时间复杂度是 $O(n^3)$,一共 $n$ 个状态,每个状态从 $O(n)$ 个状态转移,另外查找字符串是否存在还需要 $O(n)$ 的计算量,所以dp的时间复杂度是 $O(n^3)$。
由于总方案数在最坏情况下是指数级的,所以 DFS 的时间复杂度也是指数级的。
所以这道题目的总时间复杂度是指数级的。
yxcnb!
代码里dp的复杂度你还没改呢hh
哦。。我用hashset查的。。第一反应是n^2