【LeetCode & 剑指offer刷题】动态规划与贪婪法题15:Word Break(系列)
【LeetCode & 剑指offer 刷题笔记】目录(持续更新中...)
Word Break(系列)
Word Break
Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
Note:
-
The same word in the dictionary may be reused multiple times in the segmentation.
-
You may assume the dictionary does not contain duplicate words.
Example 1:
Input: s = "leetcode", wordDict = ["leet", "code"]
Output: true
Explanation: Return true because "leetcode" can be segmented as "leet code".
Example 2:
Input: s = "applepenapple", wordDict = ["apple", "pen"]
Output: true
Explanation: Return true because "applepenapple" can be segmented as "apple pen apple".
Note that you are allowed to reuse a dictionary word.
Example 3:
Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: false
/*
问题:拆分词句,看给定的词句能分被拆分成字典里面的内容
方法:动态规划
f[i]表示s[0,i-1]是否可以被分词,表示在第i个字符后面的隔板
状态转移方程:f(i) = any_of(f(j)&&s[j,i-1] ∈ dict); j = 0~i-1
例:
Input: s = "leetcode", wordDict = ["leet", "code"]
f[0] = true
i=1,j=0: l
i=2,j=0: le
j=1: e
i=3,j=0: lee
j=1: ee
j=2: e
i=4,j=0: leet f[4] = true
j=1: eet
j=2: et
j=3: t
...
O(n^2)
假设总共有n个字符串,并且字典是用HashSet来维护,那么总共需要n次迭代,每次迭代需要一个取子串的O(i)操作,然后检测i个子串,而检测是constant操作。所以总的时间复杂度是O(n^2)(i的累加仍然是n^2量级),而空间复杂度则是字符串的数量,即O(n)(本题还需加上字典的空间)
*/
class Solution
{
public:
bool wordBreak(string s, vector<string> &wordDict)
{
unordered_set<string> new_dict(wordDict.begin(), wordDict.end());//转化为哈希表,方便查找
//长度为n的字符串有n+1个隔板,多分配一个空间以方便后续递推
vector<bool> f(s.size() + 1, false);
f[0] = true; // 空字符串,初始化为true,以便后续迭代
for (int i = 1; i < f.size(); i++) //以s[i-1]字符结尾的子串, i=1~n
{
for (int j = 0; j < i; j++) //以s[j]开头的子串,j=0~i-1
{
if (f[j] && new_dict.find(s.substr(j, i-j)) != new_dict.end())//substr[start,len)
{
f[i] = true;
break;
}
}
}
return f[s.size()];
}
};
Word Break II
Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, add spaces in s to construct a sentence where each word is a valid dictionary word. Return all such possible sentences.
Note:
-
The same word in the dictionary may be reused multiple times in the segmentation.
-
You may assume the dictionary does not contain duplicate words.
Example 1:
Input:
s = "catsanddog"
wordDict = ["cat", "cats", "and", "sand", "dog"]
Output:
[
"cats and dog",
"cat sand dog"
]
Example 2:
Input:
s = "pineapplepenapple"
wordDict = ["apple", "pen", "applepen", "pine", "pineapple"]
Output:
[
"pine apple pen apple",
"pineapple pen apple",
"pine applepen apple"
]
Explanation: Note that you are allowed to reuse a dictionary word.
Example 3:
Input:
s = "catsandog"
wordDict = ["cats", "dog", "sand", "and", "cat"]
Output:
[]
/*
问题:拆分词句2,返回所有分隔结果
方法:dfs
联系排列组合问题
这里用一个hash表避免对相同子串s进行重复分隔,减少重复计算
*/
class Solution {
public:
vector<string> wordBreak(string s, vector<string>& wordDict)
{
unordered_map<string, vector<string>> m;
return dfs(s, wordDict, m);
}
vector<string> dfs(string s, vector<string>& wordDict, unordered_map<string, vector<string>>& m)
{
if (m.find(s) != m.end()) return m[s]; //如果对s的分隔已经递归过了,就直接退出
if (s.empty()) return {""}; //map型数据类型用{},递归的出口
vector<string> res; //某一次的分隔结果
for (string word : wordDict) //遍历字典中的单词(递归的分支)
{
if (word == s.substr(0, word.size()) )//如果当前单词在s开头
{
//substr 返回子串 [pos, pos+count) 。若请求的子串越过 string 的结尾,或若 count == npos ,则返回的子串为 [pos, size())
vector<string> rem = dfs(s.substr(word.size()), wordDict, m); //对该单词后面的子串递归(分支的深度),返回后面子串的分隔结果
for (string str : rem) //拼接后面子串的分隔结果与当前单词
{
res.push_back(word + (str.empty() ? "" : " ") + str); //将word和str push到结果向量中,中间用空格隔开,此为某一种结果
}
}
}
return m[s] = res; //返回对s的分隔结果
}
};