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()];
}
}