• [LeetCode 139] Word Break


    Given a string s and a dictionary of words dict, determine if s can be break into a space-separated sequence of one or more dictionary words.

    Example

    Given s = "lintcode", dict = ["lint", "code"].

    Return true because "lintcode" can be break as "lint code".

     

    Solution 1. Recursion

    Algorithm:

    For given string s of length n, we first check if s.substring(0, 1) is in dict. If it is, solve a smaller subproblem of checking if s.substring(1, n) is breakable in dict. If s.substring(1, n) is breakable, stop checking and return true; Then check if s.substring(0, 2) is in dict. If it is, solve a smaller subproblem of checking if s.substring(2, n) is breakable in dict. If s.substring(2, n) is breakable, stop checking and return true; and so on.......

     

    We lastly check if s.substring(0, n) is in dict. If it is, solve a smaller subproblem of checking if s.substring(n) is breakable in dict.  s.substring(n) is the base case where all characters have been checked.

     

    However this recursion solution is not efficient as it does a lot of redundant work. For example, for a given string of length 5. 

    s.substring(0, 1) in dict or not && wordBreak on s.substring(1, 5);

    s.substring(0, 2) in dict or not && wordBreak on s.substring(2, 5);  

    s.substring(0, 3) in dict or not && wordBreak on s.substring(3, 5);

    s.substring(0, 4) in dict or not && wordBreak on s.substring(4, 5);

    s.substring(0, 5) in dict or not && wordBreak on s.substring(5);

     

    To solve the subproblem of wordBreak on s.substring(2, 5), the following subproblems are solved.

    s.substring(2, 3) in dict or not && wordBreak on s.substring(3, 5);

    s.substring(2, 4) in dict or not && wordBreak on s.substring(4, 5);  

    s.substring(2, 5) in dict or not && wordBreak on s.substring(5);

     

    To solve subproblem of s.substring(2, 5) we have to solve subproblem of s.substring(3, 5);

    Then after s.substring(2, 5) is solved, s.substring(3, 5) is redundantly solved again as highlighted.

     

     1 public class Solution {
     2     public boolean wordBreak(String s, Set<String> dict) {
     3         if(s == null){
     4             return false;
     5         }
     6         if(s.length() == 0 && dict.size() == 0){
     7             return true;
     8         }
     9         return wordBreakRecursion(s, dict, 0);
    10     }
    11     private boolean wordBreakRecursion(String s, Set<String> dict, int startIdx){
    12         if(startIdx >= s.length()){
    13             return true;
    14         }
    15         for(int i = startIdx; i < s.length(); i++){
    16             if(dict.contains(s.substring(startIdx, i + 1))){
    17                 if(wordBreakRecursion(s, dict, i + 1)){
    18                     return true;
    19                 }  
    20             }
    21         }
    22         return false;
    23     }
    24 }

     

     Solution 2. Dynamic Programming

    To avoid the redundancy, we should use dynamic programming. 

     

    For a given string of length n, if it is breakable, then the last word after breaking must ends at the last character of the given string. We can control the length of the last breakable word from 1 to n.

     

    State: dp[i]: if s[0....... i - 1] can be broken into words in dict.

    Function: dp[i] = true if dp[i - lastWordLen] && dict.contains(s.subtring(i - lastWordLen, i)) for any lastWordLen in [1, i]. if we've already set dp[i] to true, then we skip and procced to calculate dp[i + 1].

    dp[i - lastWordLen]: if the remaining part excluding the last broke word is breakable or not.

    dict.contains(s.subtring(i - lastWordLen, i)): if the last break word of length lastWordLen exists in dict.

    Initialization:

    dp[0] = true;

    Answer: dp[s.length()];

     

    One more optimization trick is to calculate the length of the longest word. Then the algorithm can only break word of at most this length.

     

     1 public class Solution {
     2     public boolean wordBreak(String s, Set<String> dict) {
     3         if(s.length() == 0 && dict.size() == 0){
     4             return true;
     5         }
     6         int n = s.length();
     7         int maxWordLen = getMaxWordLen(dict);
     8         boolean[] dp = new boolean[n + 1]; 
     9         dp[0] = true;
    10         for(int i = 1; i <= n; i++){
    11             dp[i] = false;
    12             for(int lastWordLen = 1; lastWordLen <= maxWordLen && lastWordLen <= i; lastWordLen++){
    13                 if(dp[i - lastWordLen] && dict.contains(s.substring(i - lastWordLen, i))){
    14                     dp[i] = true;
    15                     break;
    16                 }
    17             }
    18         }
    19         return dp[n];
    20     }
    21     private int getMaxWordLen(Set<String> dict){
    22         int maxLen = 0;
    23         for(String word : dict){
    24             maxLen = Math.max(maxLen, word.length());
    25         }
    26         return maxLen;
    27     }
    28 }

     

    Solution 3. Dynamic Programming, Start to break word first from the beginning of the give string.

    The following solution does not use a similar optimization trick as in solution 2, Can you think of a similiar optimization approach?

     

     1 public class Solution {
     2     public boolean wordBreak(String s, Set<String> dict) {
     3         if(s.length() == 0 && dict.size() == 0){
     4             return true;
     5         }
     6         int n = s.length();
     7         boolean[] dp = new boolean[n + 1];
     8         dp[0] = true;
     9         for(int i = 1; i <= n; i++){
    10             dp[i] = false;
    11             for(int j = 0;  j < i; j++){
    12                 if(dp[j] && dict.contains(s.substring(j, i))){
    13                     dp[i] = true;
    14                     break;
    15                 }
    16             }
    17         }
    18         return dp[n];
    19     }
    20 }

     

    Solution 4. Dynamic Programming, with O(n^2) space, n is the length of the given string.

    Algorithm: This dp approach is different with the above approaches.  The core idea is to solve smaller length of substring problems first, then solve bigger length of substring problems.

     

    State: T[i][j]:  if s.substring(i, j + 1) can be broke into words in dict.

    Function: T[i][j] = true, if s.substring(i, j + 1) in dict or there is a k in [i, j) that satisfies T[i][k] == true && T[k][j] == true

    Initialization: T[i][j] = false;

    Answer: T[0][n - 1]

     

     1 public class Solution {
     2     public boolean wordBreak(String s, Set<String> dict) {
     3         if(s.length() == 0 && dict.size() == 0){
     4             return true;
     5         }
     6         int n = s.length();
     7         boolean[][] T = new boolean[n][n];
     8         for(int len = 1; len <= n; len++){
     9             for(int i = 0; i <= n - len; i++){
    10                 if(dict.contains(s.substring(i, i + len))){
    11                     T[i][i + len - 1] = true;    
    12                 }
    13                 else{
    14                     for(int k = i; k < i + len - 1; k++){
    15                         if(T[i][k] && T[k + 1][i + len - 1]){
    16                             T[i][i + len - 1] = true;
    17                             break;
    18                         }
    19                     }
    20                 }
    21             }            
    22         }
    23         return T[0][n - 1];
    24     }
    25 }

     

     

    Related Problems

    Word Break II

  • 相关阅读:
    typescript学习笔记(一)----基础类型
    iOS----------常用三方库
    iOS----------拨打电话的3种方式
    iOS----------随机色
    iOS----------常见经典错误
    iOS----------使用cocoapods遇到的问题
    iOS ----------NSDate 、CFAbsoluteTimeGetCurrent、CACurrentMediaTime 的区别
    iOS----------计算一段代码执行时间
    iOS----------The Apple Developer Program License Agreement has been updated.
    iOS-UIView指定圆角设置
  • 原文地址:https://www.cnblogs.com/lz87/p/7268062.html
Copyright © 2020-2023  润新知