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就更好了。