zoukankan      html  css  js  c++  java
  • 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代码链接

  • 相关阅读:
    【JavaScript框架封装】实现一个类似于JQuery的动画框架的封装
    【JavaScript框架封装】实现一个类似于JQuery的DOM框架的封装
    【JavaScript框架封装】实现一个类似于JQuery的内容框架的封装
    【JavaScript框架封装】实现一个类似于JQuery的属性框架的封装
    【JavaScript框架封装】实现一个类似于JQuery的选择框架的封装
    【JavaScript框架封装】实现一个类似于JQuery的CSS样式框架的封装
    【JavaScript框架封装】实现一个类似于JQuery的事件框架的封装
    【JavaScript框架封装】在实现一个自己定义类似于JQuery的append()函数的时候遇到的问题及解决方案
    【JavaScript框架封装】在实现一个自己定义类似于JQuery的append()函数的时候遇到的问题及解决方案
    【JavaScript框架封装】公共框架的封装
  • 原文地址:https://www.cnblogs.com/yuzhangcmu/p/4037299.html
Copyright © 2011-2022 走看看