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".
0,問題介紹
給定一個包含了若干單詞的字典以及一個字符串,將該字符串分割,分割后的得到的單詞必須由字典中的單詞組成。
1,動態規划思想的介紹---參照《算法導論》中關於動態規划的分析來分析此問題
①最優子結構
要判斷整個字符串 s[0..length] 能否被分割,可先判斷 s[0...length -1] 能否被分割;而判斷 s[0...length -1] 能否被分割,可先判 s[0...length-2] 能否被分割,……直至判斷 s[0] 能否被分割,而 s[0] 能否被分割是顯而易見的---(查找 s[0] 在不在字典中即可--dict.contains(s[0])???)
遞歸表達式如下:

②子問題的總個數是多少?每個子問題可以有多少種選擇?算法的時間復雜度是多少?
子問題的個數一個有 n 個,n為字符串的長度。每個子問題有 2 種選擇,即要么可以分割,要么不可以分割。從這個角度來看,算法的時間復雜度為 O(n)。但是,這里沒有考慮每個子問題做選擇時,需要執行多少步驟,代價是多大?從下面代碼的第 18 行 for 循環中可以看出,第 i 個子問題 需要循環 i 次,那么時間復雜度為 1+2+3+……+n = O(n^2)。
③用到了 動態規划中的重疊子問題
動態規划中的重疊子問題是指,在將原問題分解的過程中,得到了若干子問題,再將這些子問題分解時,又得到若干更小子問題……,這些子問題中,有很多是重復的。這個特點與分治算法分解問題時不同,分治算法分解得到的子問題一般是獨立的,各個子問題之間沒有太多聯系。基於動態規划子問題的重復性,因此,在求解出某個子問題之后,將它的結果記錄下來,當下一次再碰到此問題時,直接查找它的結果,而不是再一次計算該子問題。
在 wordBreak 問題中的第2點動態規划求解思路分析(下面 第2點)中,求解 match[i] 的值可能需要用到 match[i-1]、match[i-2]、……match[0]的值;
求解match[i-1]的值 可能需要用到 match[i-2]、match[i-3]、match[0]。match[i] 即對應 s[0..i-1]能否分割這個問題,再結合動態規划自底向上求解問題的性質,把"底層"問題的求解結果記錄下來,在求解到“上層”問題時,查找“底層”問題的結果,這種方式可以提高算法的運行效率。這也是《算法導論》中求LCS問題中提到的“查表”。具體的代碼體現如下:求mathc[i]的值時,需要查找match[j]的值是多少。
for (int i = 1; i < length + 1; i++) { for (int j = 0; j < i; j++) { if (match[j] && wordDict.contains(s.substring(j, i))) { match[i] = true;
2,用動態規划求解的思路
①match[s.length] 用來表示字符串 s[0...length-1] 每部分能否分割。初始時,match[0]=true; match[i] 表示 s[0...i-1] 這段字符能否分割。
match[s.length] 則表示整個字符串(s[0...length-1])能否分割。
②若match[i]=true,表示 s[0...i-1]能夠分割,則需要滿足以下條件中的任一 一個:
a)match[0]==true && s[0...i-1] 在字典中;b)match[1] == true && s[1...i-1] 在字典中;c)match[2] == true && s[2...i-1]在字典中;.....
d)match[i-1]==true && s[i-1] 在字典中。 s[i...j]在字典中表示:字符串s中由下標為 i 到 j 的子字符串是字典中的某個單詞。
具體分析:設 s = "aaaaa",dict = ["aaaa","aaa"]
由於 "a" 不在dict中,故match[1] = false; "aa" 不在dict中 且 (match[1]=false && "a" 不在dict中),故match[2]=false,對於 match[3]的值,
先判斷 "aaa" 是否在dict中,由於 "aaa"在dict 中,故 match[3] = true,對於match[4],由於"aaaa"在dict中故 match[4] = true,
對於 match[5],先判斷 "aaaaa",由於它不屬於dict ;再繼續判斷,(match[1] = true?) and (s[1...5] exist in dict?),雖然,s[1...5]="aaaa" exist in dict 為true,但是 match[1] = false,故此時還不能判斷match[5];
將 "aaaaa" 先分成 "a" ,再分成 "aaaa" 是否可以? 由於 "a" 不在 dict中 因為,match[1] == false 盡管 "aaaa" 在 dict 中,但還是故不能這樣分,因為 "a" 不在 dict 中
故match[2] 賦值為 false
再繼續判斷,(match[2] = true?) and (s[2...5] exist in dict?)....
將 "aaaaa" 先分成 "aa",再分成 "aaa",是否可以? 由於 "aa" 不在 dict 中,因為判斷match[2] == false. 盡管 "aaa" 在dict 中,但還是不能這樣分 故match[3] 賦值為 false
直至判斷到
match[4]==true? and s[4...4] == "a" exist in dict?? 此時,盡管match[4] == true 但是 s[4]== "a" 為 false,故match[5]= false.
即對於 設 s = "aaaaa",dict = ["aaaa","aaa"],程序將返回false.
3,完整代碼如下:
1 import java.util.HashSet; 2 import java.util.Set; 3 4 public class Solution { 5 6 public boolean wordBreak(String s, Set<String> wordDict) { 7 // 參數校驗 8 if (s == null || s.length() < 1 || wordDict == null || wordDict.size() < 1) { 9 return false; 10 } 11 12 // 標記是否匹配,match[i]表示 str[0, i-1]可以分割 13 int length = s.length(); 14 boolean[] match = new boolean[length + 1]; 15 match[0] = true; 16 17 for (int i = 1; i < length + 1; i++) { 18 for (int j = 0; j < i; j++) { 19 if (match[j] && wordDict.contains(s.substring(j, i))) { 20 // s(0,n) = s(0,i) + s(i,j) + s(j,n) 21 match[i] = true; 22 break; 23 } 24 } 25 } 26 return match[length]; 27 } 28 public static void main(String[] args) { 29 Solution s = new Solution(); 30 Set<String> set = new HashSet<String>(); 31 set.add("aaaa"); 32 set.add("aaa"); 33 boolean result = s.wordBreak("aaaaa", set); 34 System.out.println(result); 35 } 36 37 }
