Leetcode 139. 单词拆分
问题描述
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。
[
1
]
^{[1]}
[1]
解题报告
动态规划解决,dp[i]
表示以第 i
个字符结尾的字符串是否是可拆分的。
那么如何求 dp[i]
呢?如果我们能找到第 j
个字符,使得以第 j
个字符结尾的字符串是可拆分的且第 j
个字符到第 i
个字符组成的字符串能够在字典中找到,那么以第 i
个字符结尾的字符串是可拆分的。
实现代码
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
int n=s.size();
set<string>setWords;
for(int i=0;i<wordDict.size();i++){
setWords.insert(wordDict[i]);
}
vector<int>dp(n+1,0);
dp[0]=1;
for(int i=1;i<=n;i++){
for(int j=i-1;j>=0;j--){
if(dp[j]&&setWords.find(s.substr(j,i-j))!=setWords.end()){
dp[i]=1;
break;
}
}
}
return dp[n];
}
};
Leetcode 140. 单词拆分 II
问题描述
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。
说明:
分隔时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入:
s = “catsanddog”
wordDict = [“cat”, “cats”, “and”, “sand”, “dog”]
输出:
[
“cats and dog”,
“cat sand dog”
]
[
2
]
^{[2]}
[2]
解题报告
上一题是判断是否可分,这道题需要输出具体的单词拆分方式。直觉上按照上一题的动态规划
是不怎么好解决。【UP:其实动态规划是可以实现的,是我太愚昧\了,定义 vectordp[] ,dp[i] 表示以第 i 个字符开始的切分方式,具体参考官方题解】
最好的方式是采用DFS+回溯。
具体怎么操作呢?
- 从第
0
个字符开始搜索,当存在j
使得s[0;j]
在字典中能够找到,然后从j+1
个单词开始搜索,当搜索到底时看能否找到一个单词的拆分方式。 - 这种方法明显是需要回溯的,从第
0
个字符开始搜索,有可能找到第i
个字符使得s[0;i]
在字典中能够找到,那么接下来就可以从i+1
个单词开始搜索。
这种深搜加回溯的方法还是存在很多重复搜索的。比如 词典是:["cat", "cats", "and", "sand", "dog"]
,字符串是 catsanddog
,一条路径是:(1)从 c
开始,到 t
结束,然后从 s
出发,到 d
结束,最后从 d
出发,到 g
结束;另一条路径是:(2)从 c
开始,到 s
结束,然后从 a
开始,到 d
结束,最后从 d
开始,到 g
结束。
观察上面这两条路径发现两条路径中最后都球了一遍【从 d
开始,到 g
结束】。最好的优化方法是 记忆化
,如果从第 i
个字符开始的切分方式已经求过,那么直接拿出,就不继续递归到从 i+1
个字符开始。
[
3
]
^{[3]}
[3]
实现代码
- 我实现的 深搜+DFS,TLE。
class Solution {
public:
vector<string> wordBreak(string s, vector<string>& wordDict) {
vector<string>ans,curStr;
set<string>wordSet;
for(int i=0;i<wordDict.size();i++)wordSet.insert(wordDict[i]);
dfs(ans, s, 0, 0, curStr, wordSet);
return ans;
}
void dfs(vector<string>&ans, string s, int st, int ed, vector<string> curStr, set<string>&wordSet){
if(ed>=s.size()){
string tmp;
for(int i=0;i<curStr.size()-1;i++) tmp+=curStr[i];
ans.push_back(tmp);
}
else{
for(;ed<s.size();ed++){
if(wordSet.find(s.substr(st,ed-st+1))!=wordSet.end()){
curStr.push_back(s.substr(st, ed-st+1));
curStr.push_back(" ");
dfs(ans, s, ed+1, ed+1, curStr, wordSet);
curStr.pop_back();
curStr.pop_back();
}
}
}
}
};
- 官方题解,直接深搜。
class Solution {
public:
vector<string> wordBreak(string s, vector<string>& wordDict) {
set<string>wordSet;
for(int i=0;i<wordDict.size();i++)wordSet.insert(wordDict[i]);
word_Break(s, wordSet, 0);
return map[0];
}
unordered_map<int, vector<string>> map;
vector<string> word_Break(string s, set<string> wordDict, int start) {
if (map.find(start)!=map.end()){
// cout<<start<<" ";
return map[start];
}
vector<string> res;
# 如果不加这两段代码的话,当从 size 个字符开始搜索时,搜索结果为空,返回
# list为空,这样的话,从上一个字符开始搜索的结果就无法取到。
# res.push_back(s.substr(start, end-start+1) + (l=="" ? "" : " ") + l);不会执行
if (start == s.size()) {
res.push_back("");
}
for (int end = start; end <= s.length(); end++) {
if (wordDict.find(s.substr(start, end-start+1))!=wordDict.end()) {
vector<string> list = word_Break(s, wordDict, end+1);
for (auto l : list) {
res.push_back(s.substr(start, end-start+1) + (l=="" ? "" : " ") + l);
}
}
}
map[start]=res;
return res;
}
};
// 作者:LeetCode
// 链接:https://leetcode-cn.com/problems/word-break-ii/solution/dan-ci-chai-fen-ii-by-leetcode/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
参考资料
[1] Leetcode 139. 单词拆分
[2] Leetcode 140. 单词拆分 II
[3] Leetcode 140. 单次拆分 II:官方题解