動態規划的思想來求解字符串分割問題


LeetCode WordBreak原題

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     }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM