[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

posted @ 2017-08-01 15:36  Review->Improve  阅读(337)  评论(0编辑  收藏  举报