单词拆分 II
题目链接: https://leetcode-cn.com/problems/word-break-ii/
比单词拆分要难一点, 本质来说也是一个记忆化搜索, 但是需要用到哈希表来加速
题意: 把能拆分的单词间隔空格编程句子, 返回所有可能的句子
说明:
- 分隔时可以重复使用字典中的单词。
- 你可以假设字典中没有重复的单词。
思路:
见代码, 思路要清晰, 关键在于 map 的定义, 用下标对应下标后面的句子集来进行加速, 否则要T
对于字符串 s,如果某个前缀是单词列表中的单词,则拆分出该单词,然后对 s 的剩余部分继续拆分。如果可以将整个字符串 ss 拆分成单词列表中的单词,则得到一个句子。在对 s 的剩余部分拆分得到一个句子之后,将拆分出的第一个单词(即 s 的前缀)添加到句子的头部,即可得到一个完整的句子。上述过程可以通过回溯实现。
假设字符串 s 的长度为 n,回溯的时间复杂度在最坏情况下高达 O(n^n)
时间复杂度高的原因是存在大量重复计算,可以通过记忆化的方式降低时间复杂度。
具体做法是,使用哈希表存储字符串 s 的每个下标和从该下标开始的部分可以组成的句子列表,在回溯过程中如果遇到已经访问过的下标,则可以直接从哈希表得到结果,而不需要重复计算。如果到某个下标发现无法匹配,则哈希表中该下标对应的是空列表,因此可以对不能拆分的情况进行剪枝优化。
还有一个可优化之处为使用哈希集合存储单词列表中的单词,这样在判断一个字符串是否是单词列表中的单词时只需要判断该字符串是否在哈希集合中即可,而不再需要遍历单词列表。
class Solution {
public List<String> wordBreak(String s, List<String> wordDict) {
// 以 s 中的下标 i 为 key, 对应 s 中 i 后面的字符串能组成的句子集
Map<Integer, List<List<String>>> map = new HashMap();
// 开始递归
List<List<String>> wordBreaks = backtrack(s, s.length(), new HashSet<String>(wordDict), 0, map);
List<String> breakList = new LinkedList<String>();
for (List<String> wordBreak : wordBreaks) {
// join 方法可以让元素之间用第一参数进行分隔
breakList.add(String.join(" ", wordBreak));
}
return breakList;
}
public List<List<String>> backtrack(String s, int length, Set<String> wordSet, int index, Map<Integer, List<List<String>>> map) {
// 如果键值对中存在当前的 index 说明被记忆过
if (!map.containsKey(index)) {
List<List<String>> wordBreaks = new LinkedList<List<String>>();
// 加一个空元素, 预防后面组装句子时候的空指针(个人理解)
if (index == length) {
wordBreaks.add(new LinkedList<String>());
}
// 不是最后一个元素, 就开始遍历找 然后递归找
for (int i = index + 1; i <= length; i++) {
String word = s.substring(index, i);
// 如果找到了这样的单词, 就对接下来的部分继续递归
if (wordSet.contains(word)) {
// 继续递归
List<List<String>> nextWordBreaks = backtrack(s, length, wordSet, i, map);
// 组装句子
for (List<String> nextWordBreak : nextWordBreaks) {
// 对找到的句子加上第一个单词组装起来
LinkedList<String> wordBreak = new LinkedList<String>(nextWordBreak);
wordBreak.offerFirst(word);
// 把找到的完整句子加入到 map 中
wordBreaks.add(wordBreak);
}
}
}
// 更新键值对
map.put(index, wordBreaks);
}
return map.get(index);
}
}
总结:
哈希表加速, 记忆化搜索, map !!!
class Solution {
private:
unordered_map<int, vector<string>> ans;
unordered_set<string> wordSet;
public:
vector<string> wordBreak(string s, vector<string>& wordDict) {
wordSet = unordered_set(wordDict.begin(), wordDict.end());
backtrack(s, 0);
return ans[0];
}
void backtrack(const string& s, int index) {
if (!ans.count(index)) {
if (index == s.size()) {
ans[index] = {
""};
return;
}
ans[index] = {
};
for (int i = index + 1; i <= s.size(); ++i) {
string word = s.substr(index, i - index);
if (wordSet.count(word)) {
backtrack(s, i);
for (const string& succ: ans[i]) {
ans[index].push_back(succ.empty() ? word : word + " " + succ);
}
}
}
}
}
};