leetcode 139. 单词拆分 medium
题目描述:
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
注意你可以重复使用字典中的单词。
解题思路:
方法一: 回溯,一开始想到这个方法,但是复杂度太高了 O(n^n)
可以用记忆数组来优化,这里我们的记忆数组 memo[i] 定义为范围为 [i, n] 的子字符串是否可以拆分,初始化为 -1,表示没有计算过,如果可以拆分,则赋值为1,反之为0。
方法二: dp O(n^2)
对于这种单一字符串假如用dp求解,一般都是求以每个位置为结尾的子串的最优解,那么这道题也不例外。
状态转移方程: dp【i】表示 0~i-1形成的子串能否由字典dict中的单词组成组成,我们只要枚举0~i-1形成的子串它的所有后缀,看能否在字典中找到对应的单词,并且子串的前半部分也就是 dp【j】也等于true,就ojbk了
代码:
//dp
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> hash(wordDict.begin(),wordDict.end()); //转为hash表,好找
int n=s.size();
vector<bool> dp(n+1,false);
dp[0]=true;
for(int i=1;i<dp.size();++i){ // i是子串长度,也是下标 0~i-1形成的子串
for(int j=0;j<=i-1;++j){ //j:内部子串起点下标 枚举这个子串的后缀
string str=s.substr(j,i-j);
if(hash.count(str) && dp[j]){ //
dp[i]=true;
break;
}
}
}
return dp.back();
}
};
//一开始的回溯
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
return dfs(s,0,wordDict);
}
bool dfs(string &s,int start,vector<string> &wordDict){
if(start>=s.size()) return true;
for(auto str:wordDict){
int len=str.size();
if(s.substr(start,len)==str){
if(dfs(s,start+len,wordDict))
return true;
}
}
return false;
}
};
//用记忆memo优化 回溯
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
vector<int> memo(s.size(), -1);
return check(s, wordSet, 0, memo);
}
bool check(string s, unordered_set<string>& wordSet, int start, vector<int>& memo) {
if (start >= s.size()) return true;
if (memo[start] != -1) return memo[start];
for (int i = start + 1; i <= s.size(); ++i) {
if (wordSet.count(s.substr(start, i - start)) && check(s, wordSet, i, memo)) {
return memo[start] = 1;
}
}
return memo[start] = 0;
}
};
leetcode 140. 单词拆分 II hard
题目描述:
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。
说明:
分隔时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入:
s = "catsanddog"
wordDict = ["cat", "cats", "and", "sand", "dog"]
输出:
[
"cats and dog",
"cat sand dog"
]
解题思路:
这种要求输出所有解的题,肯定不能用dp来做,得用回溯法,所以我们直接在第一题的递归解上面修改一下就ok了,并且有个小tips,先用dp判断一下有无解,有解再dfs
代码:
class Solution {
public:
vector<string> wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> hash(wordDict.begin(),wordDict.end()); //转为hash表,好找
int n=s.size();
vector<bool> dp(n+1,false);
dp[0]=true;
for(int i=1;i<dp.size();++i){ // i是子串长度,也是下标 0~i-1形成的子串
for(int j=0;j<=i-1;++j){ //j:内部子串起点下标 枚举这个子串的后缀
string str=s.substr(j,i-j);
if(hash.count(str) && dp[j]){ //
dp[i]=true;
break;
}
}
}
if(dp.back()==false) return {}; // 先判断一下有无解 ,加速代码
vector<string> ret;
string one;
dfs(s, one, wordDict, ret, 0);
return ret;
}
void dfs(string &s, string &one, vector<string> &wordDict, vector<string> &ret, int cur){
if (cur == s.size()){
ret.push_back(one);
return;
}
if (!one.empty())
one.push_back(' ');
for (auto word: wordDict){
if (s.find(word, cur) == cur){
int cur_end = one.size();
one += word;
dfs(s, one, wordDict, ret, cur+word.size());
one.erase(cur_end, word.size());
}
}
if (!one.empty())
one.pop_back();
}
};