LeetCode: Word Break II 解題報告


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"].

解答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把對應字符串的解記下來,這樣就能避免重復的計算。 否則這一道題目會超時。

 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     }
View Code


解答2: dfs2:
參考了http://blog.csdn.net/fightforyourdream/article/details/38530983的 解法,我們仍然使用主頁君用了好多次的遞歸模板。但是在LeetCode中超時,在進入DFS時加了一個『判斷是不是wordBreak』的判斷,終於過了。這是一種DFS+剪枝的解法

 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     }
View Code

 

解答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     }
View Code

 

解答4: DP解法:

感謝大神的解法: https://gist.github.com/anonymous/92e5e613aa7b5ce3d4c5 以后再慢慢研究

主頁君自己也寫了一個先用動規算出哪些區間是可以解的,然后在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     }
View Code

 

比較與測試:

這里貼一下各種解法的時間:

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代碼鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM