[LeetCode] - Word Break

Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.

For example, given
s = "leetcode",
dict = ["leet", "code"].

Return true because "leetcode" can be segmented as "leet code".

這周對着geeks上面好幾道經典的DP題研究了一下,對於DP的題目稍微找到了一點兒感覺。只要是DP問題,就一定要先找到遞推關係式,這是最最關鍵的一步。之後便可以根據遞推關係式把原問題簡化爲子問題進行處理,並通過使用額外的內存空間來儲存計算結果,以此來消除重複計算帶來的時間上的消耗,也就是所謂的空間換時間。對於空間的使用,應該說有兩種主要的方法。

1. 儲存在一個table中。根據遞推式構建這個table,它一般是一維或者二維的。然後我們便可以按照bottom-up的順序對table的內容進行填充。所謂bottom-up,就是從table的小的index開始,逐漸往index大的方向進行填充。題目需要的結果一般就是table中的最後一個元素的值。使用table的話,一般可以寫出來iterative的程序,我覺得相對於recursive而言,稍微好理解一些。缺點就是,要對各種邊界條件考慮充分並進行適當的處理。我的經驗是,對於字符串類型的DP題目,table的大小往往要比字符串的長度大1,也就是說,在table中,字符串是1-based index。這樣往往會方便處理一些。

2. 儲存在一個cache中,然後用recursive的方式來求解。每次進行計算之前,查找cache,看看當前要計算的結果在cache裏面是否存在。如果存在,便可以直接利用了。這樣,就成功去掉了重複計算。對我個人而言,table+iterative的方式更容易理解一點兒,尤其是對於這道word break的題目。


第一段程序是以前寫的,用的cache+recursive的方法。當然,這也是一種DFS。依次取當前字符串的每一個prefix,如果它在dict中,那麼檢查cache,看看字符串相對於當前prefix而言的suffix是否在cache之中,如果不在,對其進行計算,然後將結果加入到cache之中。如果結果爲true,那麼證明這個word是可以被break的,返回true。若爲false,則證明當前的拆分不可行,繼續構建下一個prefix然後進行同樣步驟的檢查。這樣可以保證在整個過程中消除重複的計算。

public class Solution {
    public boolean wordBreak(String s, Set<String> dict) {
        if(s==null) return false;
        HashMap<String, Boolean> cache = new HashMap<String, Boolean>();
        return wordBreakHelper(s, dict, cache);
    }
    
    public boolean wordBreakHelper(String s, Set<String> dict, HashMap<String, Boolean> cache) {
        if(s.length()==0) return true;
        for(int i=1; i<=s.length(); i++) {
            String firstHalf=s.substring(0, i), secondHalf=s.substring(i, s.length());
            if(dict.contains(firstHalf)) {
                if(!cache.containsKey(secondHalf)) {
                    cache.put(secondHalf, wordBreakHelper(secondHalf, dict, cache));
                }
                if(cache.get(secondHalf)) return true;
            }
        }
        return false;
    }
}


第二段程序是這周寫的,用的是table+iterative的方法。我構建了一個一維的table,word[s.length()+1],它存儲boolean值。所以,word[i]就很自然的用來表示string(1, i)是否可以被break。注意,需要將word[0]初始化爲true,因爲假設空字符串是可以被break的,後面對table的構建也需要用到這個假設。

好了,有了這個table,接下來可以用bottom-up的方式對它進行填充了。假設word是1-based index。所以for循環從1開始一直到word.length()結束。對於每一個位置i,依次取每一個字串word(j, i),注意j是從0開始而且是要小於i的。如果發現了一個字串,它可以是的word[j]爲true,且同時是的word(j, i)在dict中,那麼word[i]就爲true,否則爲false。這裏對於i,j的選取和substring函數的使用稍微有點兒繞,要小心。將table從前到後走一遍之後,最終的結果將被保存在table的最後一個元素之中,return它即可。

我個人認爲,較之第一種方法,第二種方法更好理解一點兒,代碼也更清晰一些。當然了,都會肯定是最好的。

public class Solution {
    public boolean wordBreak(String s, Set<String> dict) {
        if(s==null) return false;
        
        // word[i] -> is s(1, i) breakable?
        boolean[] word = new boolean[s.length()+1];     // fault as default
        word[0] = true;
        for(int i=1; i<=s.length(); i++) {
            for(int j=0; j<i; j++) {
                if(word[j] && dict.contains(s.substring(j, i))) {
                    word[i] = true;
                    break;
                }
            }
        }
        return word[s.length()];
    }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章