Leetcode: Word Break II
Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each word is a valid dictionary word. Return all such possible sentences. For example, given s = "catsanddog", dict = ["cat", "cats", "and", "sand", "dog"]. A solution is ["cats and dog", "cat sand dog"].
难度:98,参考了别人的思路:这道题目要求跟Word Break比较类似,不过返回的结果不仅要知道能不能break,如果可以还要返回所有合法结果。一般来说这种要求会让动态规划的效果减弱很多,因为我们要在过程中记录下所有的合法结果,中间的操作会使得算法的复杂度不再是动态规划的两层循环,因为每次迭代中还需要不是constant的操作,最终复杂度会主要取决于结果的数量,而且还会占用大量的空间,因为不仅要保存最终结果,包括中间的合法结果也要一一保存,否则后面需要历史信息会取不到。所以这道题目我们介绍两种方法,一种是直接brute force用递归解,另一种是跟Word Break思路类似的动态规划。
类似word break中的DP做法,其实就是在word break的基础上稍作修改:
1 public class Solution { 2 public List<String> wordBreak(String s, List<String> wordDict) { 3 if (s == null || s.length() == 0) return null; 4 List<List<String>> results = new ArrayList<>(); 5 6 HashSet<String> dict = new HashSet<>(); 7 for (String word : wordDict) { 8 dict.add(word); 9 } 10 boolean[] res = new boolean[s.length()+1]; 11 res[0] = true; 12 results.add(new ArrayList<String>()); 13 results.get(0).add(""); 14 15 for (int i=1; i<=s.length(); i++) { 16 results.add(new ArrayList<String>()); 17 for (int j=0; j<i; j++) { 18 if (res[j]) { 19 String str = s.substring(j, i); 20 if (dict.contains(str)) { 21 res[i] = true; 22 for (String kk : results.get(j)) { 23 if (kk.length() == 0) 24 results.get(i).add(str); 25 else 26 results.get(i).add(kk + " " + str); 27 } 28 } 29 } 30 31 } 32 } 33 return results.get(s.length()); 34 } 35 }
还有一点需要指出的是,这个代码放到LeetCode中会MLE,但是面试应该够了,原因是LeetCode中有一个非常tricky的测试case,
Because the existence of test case 'aaaaa...aaaab', ['a', 'aa', 'aaa', ... 'aaa...aa']
, the forward DP solution will cause MLE while the backward DP is just fine, apparently the test case 'baaaaaaaa...aaaa', ['a', 'aa', 'aaa', ..., 'aaa...aa']
should also be included.
有时间的话,研究一下vote最高的做法, refer to: https://discuss.leetcode.com/topic/27855/my-concise-java-solution-based-on-memorized-dfs
Solution based on memorized DFS (Top- Down recursion), whereas my approache above is bottom-up recursion and with memorization
Using DFS directly will lead to TLE, so I just used HashMap to save the previous results to prune duplicated branches, as the following:
1 public List<String> wordBreak(String s, Set<String> wordDict) { 2 return DFS(s, wordDict, new HashMap<String, LinkedList<String>>()); 3 } 4 5 // DFS function returns an array including all substrings derived from s. 6 List<String> DFS(String s, Set<String> wordDict, HashMap<String, LinkedList<String>>map) { 7 if (map.containsKey(s)) 8 return map.get(s); 9 10 LinkedList<String>res = new LinkedList<String>(); 11 if (s.length() == 0) { 12 res.add(""); 13 return res; 14 } 15 for (String word : wordDict) { 16 if (s.startsWith(word)) { 17 List<String>sublist = DFS(s.substring(word.length()), wordDict, map); 18 for (String sub : sublist) 19 res.add(word + (sub.isEmpty() ? "" : " ") + sub); 20 } 21 } 22 map.put(s, res); 23 return res; 24 }