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.
html
For example, given
s = "catsanddog",
dict = ["cat", "cats", "and", "sand", "dog"].java
A solution is ["cats and dog", "cat sand dog"].
解答1 (dfs):
讓咱們來繼續切切切吧!
本題與上一題Word Break思路相似,可是一個是DP,一個是DFS。
讓咱們來回顧一下DP與DFS的區別:
DP是Bottom-up 而DFS是TOP-DOWN.
在本題的DFS中,咱們這樣定義:
用刀在字符串中切一刀。左邊是i個字符,右邊是len-i個字符。
i: 1- len
若是: 左邊是字典裏的詞,右邊是能夠wordbreak的,那麼把左邊的字符串加到右邊算出來的List中,生成新的list返回。
1. Base case:
當輸入字符串爲空的時候,應該給出一個空解。這個很重要,不然這個遞歸是不能運行的。
2. 遞歸的時候,i應該從1開始遞歸,由於咱們要把這個問題分解爲2個部分,若是你左邊給0,那就是死循環。
記憶:
爲了加快DFS的速度,咱們應該添加記憶,也就是說,算過的字符串不要再重複計算。舉例子:
apple n feng
app len feng
若是存在以上2種劃分,那麼feng這個字符串會被反覆計算,在這裏至少計算了2次。咱們使用一個Hashmap把對應字符串的解記下來,這樣就能避免重複的計算。 不然這一道題目會超時。git
1 // 咱們用DFS來解決這個問題吧 2 public static List<String> wordBreak1(String s, Set<String> dict) { 3 HashMap<String, List<String>> map = new HashMap<String, List<String>>(); 4 if (s == null || s.length() == 0 || dict == null) { 5 return null; 6 } 7 8 return dfs(s, dict, map); 9 } 10 11 // 解法1:咱們用DFS來解決這個問題吧 12 public static List<String> dfs(String s, Set<String> dict, HashMap<String, List<String>> map) { 13 if (map.containsKey(s)) { 14 return map.get(s); 15 } 16 17 List<String> list = new ArrayList<String>(); 18 int len = s.length(); 19 20 if (len == 0) { 21 list.add(""); 22 } else { 23 // i 表示左邊字符串的長度 24 for (int i = 1; i <= len; i++) { 25 String sub = s.substring(0, i); 26 27 // 左邊的子串能夠爲空,或是在字典內 28 if (!dict.contains(sub)) { 29 continue; 30 } 31 32 // 字符串劃分爲2邊,計算右邊的word break. 33 List<String> listRight = dfs(s.substring(i, len), dict, map); 34 35 // 右邊不能break的時候,咱們跳過. 36 if (listRight.size() == 0) { 37 continue; 38 } 39 40 // 把左字符串加到右字符串中,造成新的解. 41 for (String r: listRight) { 42 StringBuilder sb = new StringBuilder(); 43 sb.append(sub); 44 if (i != 0 && i != len) { 45 // 若是左邊爲空,或是右邊爲空,不須要貼空格 46 sb.append(" "); 47 } 48 sb.append(r); 49 list.add(sb.toString()); 50 } 51 } 52 } 53 54 map.put(s, list); 55 return list; 56 }
解答2: dfs2:
參考了http://blog.csdn.net/fightforyourdream/article/details/38530983的 解法,咱們仍然使用主頁君用了好屢次的遞歸模板。可是在LeetCode中超時,在進入DFS時加了一個『判斷是否是wordBreak』的判斷,終於過了。這是一種DFS+剪枝的解法github
1 /* 2 // 解法2:咱們用普通的遞歸模板來試一下。 3 */ 4 5 // 咱們用DFS來解決這個問題吧 6 public static List<String> wordBreak(String s, Set<String> dict) { 7 if (s == null || s.length() == 0 || dict == null) { 8 return null; 9 } 10 11 List<String> ret = new ArrayList<String>(); 12 13 // 記錄切割過程當中生成的字母 14 List<String> path = new ArrayList<String>(); 15 16 dfs2(s, dict, path, ret, 0); 17 18 return ret; 19 } 20 21 // 咱們用DFS模板來解決這個問題吧 22 public static void dfs2(String s, Set<String> dict, 23 List<String> path, List<String> ret, int index) { 24 int len = s.length(); 25 if (index == len) { 26 // 結束了。index到了末尾 27 StringBuilder sb = new StringBuilder(); 28 for (String str: path) { 29 sb.append(str); 30 sb.append(" "); 31 } 32 // remove the last " " 33 sb.deleteCharAt(sb.length() - 1); 34 ret.add(sb.toString()); 35 return; 36 } 37 38 // 若是不加上這一行會超時。就是說不能break的時候,能夠直接返回 39 // 但這也許只是一個treak, 其實這種方法仍是不大好。 40 if (!iswordBreak(s.substring(index), dict)) { 41 return; 42 } 43 44 for (int i = index; i < len; i++) { 45 // 注意這些索引的取值。左字符串的長度從0到len 46 String left = s.substring(index, i + 1); 47 if (!dict.contains(left)) { 48 // 若是左字符串不在字典中,不須要繼續遞歸 49 continue; 50 } 51 52 path.add(left); 53 dfs2(s, dict, path, ret, i + 1); 54 path.remove(path.size() - 1); 55 } 56 } 57 58 public static boolean iswordBreak(String s, Set<String> dict) { 59 if (s == null) { 60 return false; 61 } 62 63 int len = s.length(); 64 if (len == 0) { 65 return true; 66 } 67 68 boolean[] D = new boolean[len + 1]; 69 70 // initiate the DP. 注意,這裏設置爲true是不得已,由於當咱們劃分字串爲左邊爲0,右邊爲n的時候, 71 // 而右邊的n是一個字典string,那麼左邊必然要設置爲true,才能使結果爲true。因此空字符串咱們須要 72 // 認爲true 73 D[0] = true; 74 75 // D[i] 表示i長度的字符串可否被word break. 76 for (int i = 1; i <= len; i++) { 77 // 把子串劃分爲2部分,分別討論, j 表示左邊的字符串的長度 78 // 成立的條件是:左邊能夠break, 而右邊是一個字典單詞 79 D[i] = false; 80 for (int j = 0; j < i; j++) { 81 if (D[j] && dict.contains(s.substring(j, i))) { 82 // 只要找到任意一個符合條件,咱們就能夠BREAK; 表示咱們檢查的 83 // 這一個子串符合題意 84 D[i] = true; 85 break; 86 } 87 } 88 } 89 90 return D[len]; 91 }
解答3: dfs3:面試
感謝http://fisherlei.blogspot.com/2013/11/leetcode-wordbreak-ii-solution.html的解釋,咱們能夠加一個boolean的數組,b[i]表示從i到len的的字串可不能夠進行word break. 若是咱們在當前根本沒有找到任何的word, 也就代表這一串是不能word break的,記一個false在數組裏。這樣下次進入dfs這裏的時候,直接就返回一個false.經過這個剪枝咱們也能夠減小複雜度。數組
1 /* 2 // 解法3:從新剪枝。 3 */ 4 // 咱們用DFS來解決這個問題吧 5 public static List<String> wordBreak3(String s, Set<String> dict) { 6 if (s == null || s.length() == 0 || dict == null) { 7 return null; 8 } 9 10 List<String> ret = new ArrayList<String>(); 11 12 // 記錄切割過程當中生成的字母 13 List<String> path = new ArrayList<String>(); 14 15 int len = s.length(); 16 17 // 注意:必定要分配 Len+1 不然會爆哦. 18 boolean canBreak[] = new boolean[len + 1]; 19 for (int i = 0; i < len + 1; i++) { 20 canBreak[i] = true; 21 } 22 23 dfs3(s, dict, path, ret, 0, canBreak); 24 25 return ret; 26 } 27 28 // 咱們用DFS模板來解決這個問題吧 29 public static void dfs3(String s, Set<String> dict, 30 List<String> path, List<String> ret, int index, 31 boolean canBreak[]) { 32 int len = s.length(); 33 if (index == len) { 34 // 結束了。index到了末尾 35 StringBuilder sb = new StringBuilder(); 36 for (String str: path) { 37 sb.append(str); 38 sb.append(" "); 39 } 40 // remove the last " " 41 sb.deleteCharAt(sb.length() - 1); 42 ret.add(sb.toString()); 43 return; 44 } 45 46 // if can't break, we exit directly. 47 if (!canBreak[index]) { 48 return; 49 } 50 51 for (int i = index; i < len; i++) { 52 // 注意這些索引的取值。左字符串的長度從0到len 53 String left = s.substring(index, i + 1); 54 if (!dict.contains(left) || !canBreak[i + 1]) { 55 // 若是左字符串不在字典中,不須要繼續遞歸 56 continue; 57 } 58 59 // if can't find any solution, return false, other set it 60 // to be true; 61 path.add(left); 62 63 int beforeChange = ret.size(); 64 dfs3(s, dict, path, ret, i + 1, canBreak); 65 // 注意這些剪枝的代碼. 關鍵在於此以減小複雜度 66 if (ret.size() == beforeChange) { 67 canBreak[i + 1] = false; 68 } 69 path.remove(path.size() - 1); 70 } 71 }
解答4: DP解法:app
感謝大神的解法: https://gist.github.com/anonymous/92e5e613aa7b5ce3d4c5 之後再慢慢研究ide
主頁君本身也寫了一個先用動規算出哪些區間是能夠解的,而後在DFS的時候,先判斷某區間可否word break,若是不能夠,直接退出。測試
1 /* 2 // 解法4:先用DP來求解某些字段是否能word break,而後再作 3 */ 4 // 咱們用DFS來解決這個問題吧 5 public static List<String> wordBreak4(String s, Set<String> dict) { 6 if (s == null || s.length() == 0 || dict == null) { 7 return null; 8 } 9 10 List<String> ret = new ArrayList<String>(); 11 12 List<String> path = new ArrayList<String>(); 13 14 int len = s.length(); 15 16 // i: 表示從i索引開始的字串能夠word break. 17 boolean[] D = new boolean[len + 1]; 18 D[len] = true; 19 for (int i = len - 1; i >= 0; i--) { 20 for (int j = i; j <= len - 1; j++) { 21 // 左邊從i 到 j 22 D[i] = false; 23 if (D[j + 1] && dict.contains(s.substring(i, j + 1))) { 24 D[i] = true; 25 break; 26 } 27 } 28 } 29 30 dfs4(s, dict, path, ret, 0, D); 31 32 return ret; 33 } 34 35 public static void dfs4(String s, Set<String> dict, 36 List<String> path, List<String> ret, int index, 37 boolean canBreak[]) { 38 int len = s.length(); 39 if (index == len) { 40 // 結束了。index到了末尾 41 StringBuilder sb = new StringBuilder(); 42 for (String str: path) { 43 sb.append(str); 44 sb.append(" "); 45 } 46 // remove the last " " 47 sb.deleteCharAt(sb.length() - 1); 48 ret.add(sb.toString()); 49 return; 50 } 51 52 // if can't break, we exit directly. 53 if (!canBreak[index]) { 54 return; 55 } 56 57 for (int i = index; i < len; i++) { 58 // 注意這些索引的取值。左字符串的長度從0到len 59 String left = s.substring(index, i + 1); 60 if (!dict.contains(left)) { 61 // 若是左字符串不在字典中,不須要繼續遞歸 62 continue; 63 } 64 65 // if can't find any solution, return false, other set it 66 // to be true; 67 path.add(left); 68 dfs4(s, dict, path, ret, i + 1, canBreak); 69 path.remove(path.size() - 1); 70 } 71 72 }
比較與測試:ui
這裏貼一下各類解法的時間:
Test
Computing time with DFS1: 7830.0 millisec.
Computing time with DFS2: 6400.0 millisec.
Computing time with DFS3: 4728.0 millisec.
Computing time with DFS4: 4566.0 millisec.
可見,四個方法裏最好的是第四個,建議面試時能夠採用第四個。若有錯誤,敬請指正。
GitHub代碼連接