zoukankan      html  css  js  c++  java
  • [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就更好了。

  • 相关阅读:
    我的浏览器收藏夹分类
    我的浏览器收藏夹分类
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 318 最大单词长度乘积
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 316 去除重复字母
    Java实现 LeetCode 315 计算右侧小于当前元素的个数
    Java实现 LeetCode 315 计算右侧小于当前元素的个数
  • 原文地址:https://www.cnblogs.com/boris1221/p/9858740.html
Copyright © 2011-2022 走看看