https://leetcode.com/problems/word-break/
Given a non-empty string s and a dictionary wordDict containing a list of non-emptywords, 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
最直接的就是dfs搜索,但是在样例"aa....aaaaab" ["a", "aa", ...., "aaaaaaaaa"]上会TLE。
显然是因为"a"+"aa" = "aaa"类似的性质导致了大量的重复搜索,所以使用dp
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
static int fast_io = []() { std::ios::sync_with_stdio(false); cin.tie(nullptr); return 0; }();
int m_len = 0;
for(int i = 0; i < wordDict.size(); ++i){
if(wordDict[i].size() > m_len) m_len = wordDict[i].size();
}
unordered_set<string> dic(wordDict.begin(), wordDict.end());
vector<bool> dp(s.size()+1, false);
dp[0] = true;
for(int i = 1; i <= s.size(); ++i){
for(int j = max(0, i-m_len); j < i; ++j){
// cout << s.substr(j, i-j) << endl;
if(dp[j] == true && dic.find(s.substr(j, i-j)) != dic.end()){
// cout << j << ' ' << i << endl;
dp[i] = true;
break;
}
}
}
return dp[s.size()];
}
};
其实这个dp可以优化,优化点在于判断尾部是否在dic中不用调用std函数与数据结构,,别人的代码如下(4ms)
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
vector<unsigned> vals(s.size()+1, 0);
vals[0] = 1;
for (unsigned i = 0; i < s.size(); ++i) {
unsigned idx = i+1;
for (const string& word : wordDict) {
int start_idx = idx - word.size();
if (start_idx < 0 || !vals[start_idx]) {
continue;
}
unsigned j = 0;
for (; j < word.size(); ++j) {
if (s[start_idx + j] != word[j]) {
break;
}
}
vals[idx] = j == word.size();
if (j == word.size()) {
break;
}
}
}
return vals[s.size()];
}
};
以上的dp,都可以理解为针对每一个位置向前的dp,我们可以使用向后的dp,并且使用与BFS相似的技巧,在多个点“同步”向后迈,加速到达终点的情况,如下:
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet;
for(auto i:wordDict)
wordSet.insert(i);
unordered_set<int> frontier; //用于记录上一步到达的新节点
unordered_set<int> visited; //用于防止重复到达
frontier.insert(0);
while(frontier.size()) //如果上一步没有新节点被到达则false
{
unordered_set<int> temp; //用于临时记录这一步到达的新节点
for(auto i: frontier)
{
for(auto j: wordSet)
if(visited.count(i+j.size())==0 && s.substr(i,j.size())==j)
//这里就可以看出visited的作用
{
if(i+j.size()==s.size())return true;
visited.insert(i+j.size());
temp.insert(i+j.size());
}
}
temp.swap(frontier); //交换temp与frontier
}
return false;
}
};