方法一:Backtracing+Memoization
遇到在wordlist里的单词就继续深搜,memo[i] 用来保存下标i开始到结束的字符串是否能拆分,如果之前保存过,就直接返回,不用重复计算。
用start记录开始的下标,这样就不用传字符串作为参数了,当然用字符串也可以。
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 dfs(s,0,wordSet,memo); } bool dfs(string s, int start, unordered_set<string> &wordSet, 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)) && dfs(s, i, wordSet, memo)) { // [start,i-1] [i,..] return memo[start] = 1; } } return memo[start] = 0; } };
如果不用memorization,时间复杂度为 O(2^n)。
用了memorization,时间复杂度 O(n^2),
空间复杂度 O(n),递归深度最多为n。
关于memorization的时间复杂度:
For solution 2, please check whether my thought process for getting time complexity is correct or not.
If we don't use memorization the recurrence relation of time complexity will be :
T(n) = T(n-1) + T(n-2) + T(n-3) + .... + T(1)
Now if we add memorization, by the time we finish doing T(n-1), We already have the memorization result for n - 2, n - 3 ... 1.
So to check if T(n-2) is valid only takes O(1) time, same goes for the T(n-3) .....T(1)
The recurrence relation now becomes T(n) = T(n-1) + n - 2
Since we are considering big-O notation, we can drop the constant term, it now becomes T(n) = T(n-1) + n.
Using master theorem, we can easily get the time complexity for this time recurrence relation is O(n^2).
https://stackoverflow.com/questions/21273505/memoization-algorithm-time-complexity
方法二:DP
dp[i] 表示前i个字符是否能够被拆分,目标dp[n]
base case: dp[0]=true,可以把wordDict里的元素都置为true
注意本题dp[i]不是表示为下标0~i是有原因的,如果这样表示当然也可以,都需要进行修改,且较为麻烦。因此做dp问题的时候要想一想怎样比较容易写。
class Solution { public: bool wordBreak(string s, vector<string>& wordDict) { unordered_set<string> a; for (string word:wordDict) a.insert(word); vector<bool> dp(s.size()+1,false); // dp[i] 前i个元素是否能拆分 0~i-1 dp[0] = true; for (int i=1;i<=s.size();++i){ for (int j=i-1;j>=0;--j){ // 0~j-1 j~i-1 if (dp[j] && a.count(s.substr(j,i-j))){ dp[i] = true; break; } } }return dp[s.size()]; } };
时间复杂度 O(n^2)
空间复杂度 O(n)
方法三:BFS
这道题也可以bfs来做,和jump game类似,能到字符串最后说明可以拆分。
=========================================================
Word Break II
方法一:Backtracing+Memoization
class Solution { public: unordered_map<int,vector<string>> memo; // start -> vector<string> vector<string> wordBreak(string s, vector<string>& wordDict) { unordered_set<string> wordSet(wordDict.begin(), wordDict.end()); return dfs(s,0,wordSet); } vector<string> dfs(string s, int start, unordered_set<string> &wordSet){ if (memo.count(start)) return memo[start]; vector<string> res; if (start==s.size()) res.push_back(""); for (int i=start+1;i<=s.size();++i){ if (wordSet.count(s.substr(start, i-start))) { // [start,i-1] [i,..] vector<string> tmp=dfs(s, i, wordSet); for (string str:tmp){ res.push_back(s.substr(start, i-start) + (str==""?"":" ") + str); } } } memo[start] = res; return res; } };
时间复杂度 O(n^3)
空间复杂度 O(n^3) vector<string> 需要O(n^2)
方法二:DP
思路一样,只不过dp[i]里面保存,能够组成前i个的 用来拼接的word。不过这种方法空间上会超时,没法AC。
class Solution { public: vector<string> wordBreak(string s, vector<string>& wordDict) { unordered_set<string> a; for (string word:wordDict) a.insert(word); vector<vector<string>> dp(s.size()+1); dp[0].push_back(""); for (int i=1;i<=s.size();++i){ for (int j=i-1;j>=0;--j){ if (dp[j].size()!=0 && a.count(s.substr(j,i-j))){ for (string x:dp[j]){ dp[i].push_back(x + (x==""?"":" ") + s.substr(j,i-j)); } } } } return dp[s.size()]; } };
时间复杂度 O(n^3) 两层循环n^2,每次循环把数组元素加到dp[i],O(n)
空间复杂度 O(n^3)