Leetcode 140. Word Break II
Problem:
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: []
Solution: 拿到这道题的第一反映是用backtrace来做,但发现在这个测试用例上栽了跟头:
s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
wordDict = {"a","aa","aaa","aaaa","aaaaa","aaaaaa","aaaaaaa","aaaaaaaa","aaaaaaaaa","aaaaaaaaaa"}
超时代码如下:
1 class Solution { 2 public: 3 void backtrace(int index,string &s,vector<string> &path,vector<string> &result,unordered_set<string> &us){ 4 if(index == s.size()){ 5 string str = ""; 6 for(int i = 0;i != path.size();++i){ 7 str = str + path[i] + " "; 8 } 9 result.push_back(str.substr(0,str.size()-1)); 10 return; 11 } 12 string str = ""; 13 for(int i = index;i != s.size();++i){ 14 str += s[i]; 15 if(us.find(str) != us.end()){ 16 path.push_back(str); 17 backtrace(i+1,s,path,result,us); 18 path.pop_back(); 19 } 20 } 21 } 22 vector<string> wordBreak(string s, vector<string>& wordDict) { 23 int m = s.size(); 24 unordered_set<string> us; 25 for(string str:wordDict) us.insert(str); 26 vector<string> result; 27 vector<string> path; 28 backtrace(0,s,path,result,us); 29 return result; 30 } 31 };
对于这类问题,应该要想到是否能用记忆的方法来提高效率(动态规划属于记忆的特殊方式)。现在来思考发生超时的原因是什么,就这个例子而言,我们发现如果从某个位置开始,无法将后面的字符串进行分割到达最后一个字符时,那么下一次对前面的字符串通过不同的分割方案到这个位置的时候就可以提前知道,无论怎样DFS都无法完全分割后面的字符串。所以我们可以通过一个invalid的hash set记录下所有该位置开始不能到达s末尾的索引,当invalid中包含这个起始位置index时,我们就不需要执行下面的程序了,因为我们早就知道即使进行DFS我们也无法到达最后一个字符。那么判断能否到达最后一个字符的依据是什么呢,如果说在进行DFS之前和DFS之后结果集的长度没有发生变化,那么我们就可以判断这个位置开始无法产生新的分割方法,所以将该索引添加到invalid当中。
其实这个问题不算是动态规划,只能算DFS的优化,但这个问题的核心部分仍然是记忆问题,所以我把它放在动态规划这一大类下。
Code:
1 class Solution { 2 public: 3 void backtrace(int index,string &s,string path,vector<string> &result,unordered_set<string> &us,unordered_set<int> &invalid){ 4 if(invalid.find(index) != invalid.end()) 5 return; 6 if(index == s.size()){ 7 result.push_back(path.substr(0,path.size()-1)); 8 return; 9 } 10 int length = result.size(); 11 string str = ""; 12 for(int i = index;i != s.size();++i){ 13 str += s[i]; 14 if(us.find(str) != us.end()){ 15 backtrace(i+1,s,path+str+" ",result,us,invalid); 16 } 17 } 18 if(result.size() == length) 19 invalid.insert(index); 20 } 21 vector<string> wordBreak(string s, vector<string>& wordDict) { 22 int m = s.size(); 23 unordered_set<string> us; 24 unordered_set<int> invalid; 25 for(string str:wordDict) us.insert(str); 26 vector<string> result; 27 backtrace(0,s,"",result,us,invalid); 28 return result; 29 } 30 };