【DP】筆記一

【DP】筆記一

摘要

動態規劃方法通常用來求解最優化問題(optimization problem).這類問題可以有很多可行解,每個解都有一個值,我們希望尋找具有最優值(最小值或最大值)的解。我們稱這樣的解爲問題的一個最優解(an optimal solution),而不是最優解(the optimal solution),因爲可能有多個解都達到最優值。

我們通常按照如下4個步驟來設計一個動態規劃算法:

  1. 刻畫一個最優解的結構特徵。
  2. 遞歸地定義最優解的值。
  3. 計算最優解的值,通常採用自底向上的方法。
  4. 利用計算出的信息構造一個最優解。

上面這一段是從《算法導論》抄來的,做DP問題一般都會按照這個思路去搞。看起來很容易,但是我最怕的就是DP問題(當然,並不是其它問題不怕…),爲了緩解一下這種心理陰影,準備把LeetCode裏的DP問題刷完。DP不歸路剛剛開始,遇到兩個有陰影的問題,都是看完提示才搞定的,遂記之。

140 Word Break II

題目鏈接Word Break II。初看題目,就感覺子結構體不太好劃分,後面果然是卡在這上面。先後試過幾種算法。

1. 遞歸算法

​ 好像DP的問題很容易就會套用遞歸算法去解。這題也不例外,因爲最優子結構一直抽象不出來,所以先從遞歸 算法開始找感覺。傳入字符串,從字典中查找,如果有匹配的詞,則遞歸子串。很簡單,當然,不出意外, TIME OUT.

2. 加上備忘

​ 對於長的字符串,遞歸後的回溯檢查,會有大量重複的計算。因此考慮,加上備忘,提高一下回溯的效率。在每次遞歸成功後,將當前的子串和字典的組合存下來,後續再次碰到同樣的子串後,直接用對應的字典組合來生成結果。速度提升非常明顯,直到遇到aaaaaaaaaaaabaabaaa…類似這種用例,就是重複的詞重複出現,看到這個第一反應是KMP算法,但是KMP的使用場景是查找,明顯不適用。這種情況因爲備忘也會存很多,所以也會導致速度變很慢。顯然用例是故意這麼設計的,因爲輸出爲空,用意就是檢查遇到無法再繼續匹配的情況,算法的處理。在這個地方卡住了,所以又換了個思路。這次繞不開DP了,就只能往DP的思路上去靠。(其實這兩種算法在實現前我就預感到問題的存在,但是這並不值得驕傲啊。。。)對於重複子結構,我犯糊塗了,對於s[i,j]這種結構的抽象,總是得不到合理的方案,感覺距離成功只差一步。(哪一次卡殼不是呢,呵呵)直到看到了提示。。。

3. DP算法1

​ 遍歷輸入字符串,在循環中,遍歷字典,只有後向匹配成功,才從成功的位置進行組合,備忘就是到每個位置的字典組合。就是這麼簡單。。。不像之前考慮的[i,j]之間要怎樣怎樣,欲哭無淚啊。。。不過,依然TIME OUT,不要吃驚,原因跟上面的算法2一樣,對於重複詞的場景,雖然結果爲空,但中間過程備忘太大,影響效率。到了這步,當然無須再看什麼提示了。。。

4. DP算法2

​ 算法框架如上,備忘換成匹配成功的位置;在算法完成後,再根據位置對結果進行組合。該算法很好的解決上面的問題,因爲中間過程的備忘不需要拷貝。。。

174 Dungeon Game

題目鏈接Dungeon Game。初看該題的感覺,跟上題不太一樣,就是感覺會比較曲折,但是應該能搞定的那種,誰知打臉來的太快。。。

1. 遞歸算法

對於我不能解決的DP問題,我是不憚以遞歸算法來解決的。。。兩個分支,向下向右,然後TIME OUT。但是,這並不妨礙心理上會有某種自我安慰。。。

2. DP自頂向下

​ 因爲分支是固定的,只能向下和向右,所以,結構劃分是確定的;問題在於最優解的定義。本題難點在於求解所有路徑的最小生命初值的最小值。這一點對於遞歸算法不是問題,因爲遞歸會遍歷所有路徑,並存下每條路徑的最小生命初值,最終通過比較會得到一個最小值。但是DP自頂向下算法中,對於位置(i,j)的情況,在該位置可以獲取從(0,0)到該位置的最小生命初值,但是由於需要繼續往下計算,還需要記錄路徑的當前生命值(也就是從其實位置到該位置的累加值),那就存在一個問題,需要記錄哪條路徑的當前值?

  1. ​ 當前位置最小生命值的路徑值? NO,如果這都可以,那就是貪心了,本題當然不是

  2. ​ 記錄所有路徑的值? NO, 如果這都可以,這還叫DP?

  3. ​ 當前位置最大的路徑值? NO,如果這都可以。。。

  4. ​ 記錄最小生命值和最大路徑值兩條? NO,原因就是1、3的原因

  5. ​ 記錄當前位置到右側和下側的最小生命值? NO, 原因跟1沒啥區別,沒有考慮到後續路徑變化的動態過程。

  6. ​ 卡殼。。。

    這題比上題更陰影,對遞歸算法都沒法改進。。。

3. DP自底向上

​ 看到提示後,第一反應是踢自己兩腳,爲啥自己沒想到,然後纔想爲毛自底向上可以,自頂向下不行?(奇怪的人類) 本人水平有限,簡單證明一下:

​ 自底向上算法:求位置(i,j)處的最小生命值,只依賴(i+1, j) 和 (i, j + 1)兩個位置的最小生命值以及(i,j)本身,所以遞推(0,0)處的值也是可以按此方法求出的。

​ 自頂向下算法:求位置(i,j)處的最小生命值,見上面的分析,依賴所有的路徑,沒有辦法抽象出最優解。

可能感覺上面的證明跟沒說一樣,那就貼段自底向上算法最小生命值的求解方法,供意會。

if (i + 1 < dungeon.length) {    
	down = new Node();    
	down.cur = memo[i + 1][j].cur + dungeon[i][j];    
	if (memo[i + 1][j].cur_min >= 0) {        
		if (dungeon[i][j] < 0) {            
			down.cur_min = dungeon[i][j];        
		} else {            
			down.cur_min = down.cur;        
		}    
	} else {        
		down.cur_min = dungeon[i][j] + memo[i + 1][j].cur_min;    
	}
}						

總結

​ 例行總結時間:從174這道題目上看,自底向上和自頂向下對於算法還是很不一樣的,這一點顛覆了我的認知,之前一直認爲只是同一種算法的不同實現;140這道題目,說到底還是腦筋有點僵化,對於重複子結構的劃分總是拘泥於[i,j]內循環遍歷這種模式。好啦,DP不歸之路長路漫漫,慢慢耍啦。。。

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