[leetcode] Word Break

Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

Note:

  • The same word in the dictionary may be reused multiple times in the segmentation.
  • You may assume the dictionary does not contain duplicate words.

Example 1:

Input: s = "leetcode", wordDict = ["leet", "code"]
Output: true
Explanation: Return true because "leetcode" can be segmented as "leet code".

Example 2:

Input: s = "applepenapple", wordDict = ["apple", "pen"]
Output: true
Explanation: Return true because "applepenapple" can be segmented as "apple pen apple".
             Note that you are allowed to reuse a dictionary word.

Example 3:

Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: false

分析:题目翻译一下:给一个字符串和一个list,要求判断这个字符串是否可以由这个list中字符串组成。

思路1:想到利用递归的思想,比如leetcode,["leet","code"]。定义两个指针left和right,left从0开始,right从1开始,right向右走,如果遇到substring(left,right),就把剩下的字符串重新进行这样的操作,知道left==s.length()为止。

讲的比较复杂,直接看代码吧:

 1 class Solution {
 2     boolean can = false;
 3     public boolean wordBreak(String s, List<String> wordDict) {
 4         if ( s.length() == 0 ) return false;
 5         helper(s,wordDict,0,1);
 6         return can;
 7     }
 8     private void helper(String s, List<String> wordDict, int left, int right) {
 9         if ( right > s.length() ) return ;
10         while ( right <= s.length() ){
11             if ( wordDict.contains(s.substring(left,right)) ){
12                 if ( right == s.length() ) can=true;
13                 helper(s,wordDict,right,right+1);
14             }
15             right ++;
16             
17         }
18     }
19 }

    29 / 36 test cases passed。在第30个测试用例时报错: Time Limited。初步判断是因为递归太深了,导致堆栈溢出。看了一下第30个用例,很恶心的例子。。

    既然如此,说明不能使用DFS这种方法,可能性太多了,递归不是个好方法。

思路2:既然DFS可以过29个用例,只是几个递归很深的不能a,那么还是上面的思路,改成BFS可不可以呢?

BFS思路非常有趣,queue中始终保存着s中在dict中出现的子字符串的下标,比如leetcode中,0,4,7。同时需要一个visited数组保存访问过位置的记录。

代码如下:

 1 class Solution {
 2     public boolean wordBreak(String s, List<String> wordDict) {
 3         Queue<Integer> queue = new LinkedList<>();
 4         boolean[] visited = new boolean[s.length()+1];
 5         int max_length = 0;
 6         for ( String ss : wordDict )
 7             max_length = Math.max(max_length,ss.length());
 8         queue.offer(0);
 9         visited[0] = true;
10         while ( !queue.isEmpty() ){
11             int t = queue.poll();
12             for ( int j = t + 1 ; j <= s.length() && j-t<=max_length ; j ++ ){
13                 String sub = s.substring(t,j);
14                 if ( wordDict.contains(sub) && !visited[j]){
15                     if ( j == s.length() ) return true;
16                     queue.offer(j);
17                     visited[j] = true;
18                 }
19             }
20         }
21         return false;
22     }
23 }

    AC:8ms,这里使用了trick,先找到dict中最长字符串的长度。

 受此启发,如果在DFS中加入visited数组,保存访问过的记录,是否可以解决堆栈太深的问题?

代码如下:

 1 class Solution {
 2     boolean can = false;
 3     boolean[] visited;
 4     public boolean wordBreak(String s, List<String> wordDict) {
 5         if ( s.length() == 0 ) return false;
 6         visited = new boolean[s.length()+1];
 7         visited[0] = true;
 8         helper(s,wordDict,0,1);
 9         return can;
10     }
11     private void helper(String s, List<String> wordDict, int left, int right) {
12         if ( right > s.length() ) return ;
13         while ( right <= s.length() ){
14             if ( wordDict.contains(s.substring(left,right)) && !visited[right]){
15                 if ( right == s.length() ) can=true;
16                 visited[right] = true;
17                 helper(s,wordDict,right,right+1);
18             }
19             right ++;
20             
21         }
22     }
23 }

    AC:16ms。说明DFS+visited是可以解决堆栈过深的问题,因为visited保存着访问记录,对于dict中已经存在的字符串不需用第二次遍历了。所以可解。

思路3:DP方法。三要素如下:

1、dp[i]:表示0~i是否可以被break到dict中。

2、边界条件初始化:dp[0] = true

3、状态转移方程:dp[i] = dp[j] && wordDict.contains(s.substring(j,i))  for j in (0,i)   //找i位置之前的dp[j] = true并且剩下的字符串在dict中。

代码如下:

 1 class Solution {
 2    public boolean wordBreak(String s, List<String> wordDict){
 3         boolean[] dp = new boolean[s.length()+1];
 4         dp[0] = true;
 5         for ( int i = 1 ; i < dp.length ; i ++ ){
 6             for ( int j = 0 ; j < i ; j ++ ){
 7                 if ( dp[j] && wordDict.contains(s.substring(j,i)) ){
 8                     dp[i] = true;
 9                     break;
10                 }
11             }
12         }
13         return dp[dp.length-1];
14     }
15 }

    AC:8ms,估计加上max_length的限定后会到5ms左右,就不加了。

总结:其实最基础的想法应该是DP,因为是否能break到dict中跟分解有很大关系。

           DFS是一个比较容易想到的方法,如果想到dfs+visited就更好了。

 

posted @ 2018-10-28 21:16  Lin.B  阅读(193)  评论(0编辑  收藏  举报