1. 程式人生 > >LeetCode139-Word Break & LeetCode140-Word BreakII (dp)

LeetCode139-Word Break & LeetCode140-Word BreakII (dp)

LeetCode139-Word Break & LeetCode140-Word BreakII (dp)

  • LeetCode139 - 記憶化遞迴
  • LeetCode139 - dp
  • LeetCode140

LeetCode139題目連結

題目

在這裡插入圖片描述


LeetCode139 - 記憶化遞迴

遞迴函式大概的思路:

  • 字串在每個位置從左到右進行劃分成左邊和右邊部分;
  • 左邊部分遞迴的去求看是否滿足,右邊看wordDict中是否有這個單詞;
  • 因為遞迴的時候有重複的子問題,所以使用map進行記憶化;
  • 這裡是從頂(例如"leetcode"從最長求到最短
    )到下,而dp是從短推到長;
  • 這裡每個位置只需要某個劃分滿足即可,所以下面遞迴函式中如果有一個劃分滿足條件,立即就return 了;

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        if(s == null)
            return true;
        if(wordDict == null)
            return false;
        // memorize
        HashMap<
String,Boolean>
map = new HashMap<>(); return process(new StringBuilder(s),wordDict,map); } private boolean process(StringBuilder sb,List<String> dict,HashMap<String,Boolean>map){ if(dict.contains(sb.toString())) //這句話不能省略 因為下面只是判斷到 < sb.length, return
true; if(map.containsKey(sb.toString())) return map.get(sb.toString()); for(int i = 0; i < sb.length(); i++){ // for(int i = 1; i < sb.length(); i++){ 也可以寫成從1開始 因為L == ""可以不用劃分 StringBuilder L = new StringBuilder(sb.substring(0,i)); // [0,i) StringBuilder R = new StringBuilder(sb.substring(i)); // [i,sb.length) if(dict.contains(R.toString()) && process(L,dict,map)){//先判斷右半部分 map.put(sb.toString(),true); return true; } } map.put(sb.toString(),false); return false; } }

注意到,也可以反過來改成L去判斷在不在wordDict中,而右邊部分R去遞迴(這樣程式碼簡介很多,而且速度也更快)

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        if(s == null)
            return true;
        if(wordDict == null)
            return false;
        // memorize
        HashMap<String,Boolean>map = new HashMap<>();
        return process(s,wordDict,map);
    }

    //相當於s的左邊L去判斷在不在wordDict中,而右邊去遞迴
    private boolean process(String s,List<String> dict,HashMap<String,Boolean>map){
        if(s.isEmpty())
            return true;  //""返回true
        if(map.containsKey(s))
            return map.get(s);
        for(String word : dict){
            if(s.startsWith(word)){//  左邊L在wordDict中有包含
                if(process(s.substring(word.length()),dict,map)){
                    map.put(s,true);
                    return true;
                }
            }
        }
        map.put(s,false);
        return false;   
    }
}

LeetCode139 - dp

也就是從下到上的求解:

  • 注意,一開始dp.put("",true),表示的是相當於"“是返回true的,這個是必須的。因為某個劃分L是”",而R 在wordDict中。
  • 比如劃分到"leetcode"的"leet"的時候,當leet被劃分成"“和"leet"左邊就是”",右邊是"leet"(在wordDict中),所以滿足;

在這裡插入圖片描述

class Solution {
   public boolean wordBreak(String s, List<String> wordDict) {
        if(s == null)
            return true;
        if(wordDict == null)
            return false;
        StringBuilder sb = new StringBuilder(s);
        HashMap<String,Boolean> dp = new HashMap<>();

        dp.put("",true);// must 

        for(int i = 1; i <= sb.length(); i++){// 從1開始就可以 因為dp.put("",true)
            StringBuilder sbI = new StringBuilder(sb.substring(0,i));
            for (int j = 0; j < i; j++) {
                String L = sbI.substring(0, j);
                String R = sbI.substring(j);
                if ( dp.get(L) != null && dp.get(L)  && wordDict.contains(R)) {
                    dp.put(sbI.toString(), true);
                    break;
                }
            }
        }
        return dp.get(sb.toString()) == null ? false : dp.get(sb.toString());
    }
}

稍微優化的思路:

  • 上面的HashMap中的true其實是表示的dp的值,因為key為String,所以不好用陣列表示。
  • 但是其實我們可以將左邊部分的字串對映為只需要以某個位置結尾的字串就可以了。
  • 也就是說L部分求解,只需要記錄那個字串的結束位置即可,也就是可以只用一個boolean陣列求解即可。
class Solution {
   public boolean wordBreak(String s, List<String> wordDict) {
        if(s == null)
            return true;
        if(wordDict == null)
            return false;
       StringBuilder sb = new StringBuilder(s);
       boolean[] dp = new boolean[s.length() + 1];
       dp[0] = true; //  類似 dp.put("",true);
       for(int i = 1; i <= sb.length(); i++){//從1開始就可以
            String sbI = sb.substring(0,i);
            for (int j = 0; j < i; j++) {
                if(dp[j] && wordDict.contains(sbI.substring(j))){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()]; 
    }
}

再次優化,連sbI也可以省略,因為可以直接取[j,i)之間的字元作為sbI即可。

class Solution {
   public boolean wordBreak(String s, List<String> wordDict) {
        if(s == null)
            return true;
        if(wordDict == null)
            return false;
       StringBuilder sb = new StringBuilder(s);
       boolean[] dp = new boolean[s.length() + 1];
       dp[0] = true; //  類似 dp.put("",true);
        for(int i = 0; i <= sb.length(); i++){
            for (int j = 0; j < i; j++) {
                if(dp[j] && wordDict.contains(sb.substring(j,i))){//這裡簡單的優化
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()]; 
    }
}

LeetCode140

LeetCode140題目連結

題目

在這裡插入圖片描述

解析

和上題不同的是,這個題目要求出所有的組合:
記憶化遞迴的方式:

  • 在遞迴的每一層,如果R(右邊部分) 包含在wordDict中,則左邊求出的所有解(放在List中),都和右邊部分的字串組成一個新的解(新的List),並新增到結果中;
  • 注意遞迴的每一層,一開始要判斷沒有劃分的那個,也就是dict.contains(s)這句。

在這裡插入圖片描述

class Solution {
    public List<String> wordBreak(String s, List<String> wordDict) {
        if (s == null || wordDict == null)
            return new ArrayList<>();
        return process(s,new HashMap<>(),wordDict);
    }

    public List<String> process(String s,HashMap<String,List<String>>map,List<String>dict){
        if(map.containsKey(s))
            return map.get(s);
        List<String>res = new ArrayList<>();
        if(dict.contains(s))
            res.add(s);
        for(int i = 1; i < s.length(); i++){//注意這裡不需要<=因為是對每一個劃分
            String R = s.substring(i);
            if(!dict.contains(R))
                continue;
            String L = s.substring(0,i);
            List<String>LRes = process(L,map,dict); //先求出左邊的結果
            for(String si : LRes)
                res.add(si + " " + R);
        }
        map.put(s,res);
        return res;
    }
}

同理,也可以寫成下面的樣子(左邊檢視在不在wordDict中,右邊遞迴) ;

class Solution {
    public List<String> wordBreak(String s, List<String> wordDict) {
        if (s == null || wordDict == null)
            return new ArrayList<>();
        return process(s,new HashMap<>(),wordDict);
    }

    public List<String> process(String s,HashMap<String,List<String>>map,List<String>dict){
        if(map.containsKey(s))
            return map.get(s);
        List<String>res = new ArrayList<>();
        if(dict.contains(s))
            res.add(s);
        for(String word : dict){
            if(s.startsWith(word)){
                String R = s.substring(word.length());
                List<String>RRes = process(R,map,dict);
                for(String si : RRes)
                    res.add(word + " " + si);
            }
        }
        map.put(s,res);
        return res;
    }
}

改成dp的方式超記憶體了,不知道是寫錯了還是怎麼的,以後刷第二遍的時候再看看吧。。。程式碼也貼上來:

class Solution {
    
    public List<String> wordBreak(String s, List<String> wordDict) {
        if (s == null || wordDict == null)
            return new ArrayList<>();
        List<String>res = new ArrayList<>();

        HashMap<String,List<String>> dp = new HashMap<>();

        dp.put("",new ArrayList<>());

        for(int i = 1; i <= s.length(); i++){

            String sI = s.substring(0,i);

            res = new ArrayList<>();
            if(wordDict.contains(sI))
                res.add(sI);

            for(int j = 0; j < i; j++){
                String R = sI.substring(j);
                if(!wordDict.contains(R))
                    continue;
                List<String>LRes = dp.get(sI.substring(0,j));
                for(String si : LRes)
                    res.add(si + " " + R);
            }
            dp.put(sI,res);
        }
        return res;
    }
    
    /**
    public List<String> wordBreak(String s, List<String> wordDict) {
        if (s == null || wordDict == null)
            return new ArrayList<>();
        List<String>res = new ArrayList<>();

        List<List<String>> dp = new ArrayList<>();

        dp.add(new ArrayList<>());

        for(int i = 1; i <= s.length(); i++){

            String sI = s.substring(0,i);

            res = new ArrayList<>();
            if(wordDict.contains(sI))
                res.add(sI);

            for(int j = 0; j < i; j++){
                String R = sI.substring(j);
                if(!wordDict.contains(R))
                    continue;
                List<String>LRes = dp.get(j);
                for(String si : LRes)
                    res.add(si + " " + R);
            }
            dp.add(i,res);
        }
        return res;
    }
    **/
}