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

  • 相关阅读:
    ACM Red and Black
    ACM Wooden Stricks
    ACM Strange fuction
    ACM pie
    ACM Doing Homework again
    ACM FatMouse' Trade
    ACM 今年暑假不AC
    ACM Ignatius and the Princess II
    一、数组---第三大的数
    一、数组---移动零※※※※※※
  • 原文地址:https://www.cnblogs.com/boris1221/p/9858740.html
Copyright © 2011-2022 走看看