阿偉在搞藍橋杯的時候,屬實是被動態規劃按在地上摩擦、吊在樹上打,看的懂答案、但是做的時候老是白給,於是我趕緊去找大佬的文章去讀一下(可以看我下面的參考資料),下面是我自己的一些見解,希望在自己成長的同時,可以幫助到有需要的人。該文章用leetcode《53. 最大子序和》作爲開始,leetcode《5. 最長迴文子串》進階和強化,leetcode《887. 雞蛋掉落》做魔鬼訓練,實際代碼以及代碼模板均用僞代碼,後面有題目類型總結(看來源),建議點個小星星作爲收藏,方便練習
白話解動態規劃
- 1. 動態規劃究竟是要幹什麼
- 2. 動態規劃的思考步驟
- 3. 最大子序列和(入門難度,瞭解思維方式)
- 4. 最長迴文子串(medium難度,熟練一下)
- 5. 知道了刷題的套路,再回頭深入理解動態規劃的本質,爲什麼動態規劃會這麼6
- 6. 高樓砸雞蛋問題(hard難度,帶着狀態變化思考問題)
- 6.1 題目講述
- 6.2 在正式解題前,吹一波水,穩住
- 6.3 等一下,我這題直接用二分搜索不就完事了嘛
- 6.4 三個思考步驟
- 6.5 構建僞代碼
- 6.6 其實呢,這題還是有辦法二分的
- 6.7 來一波反向操作,看一下你懂了沒
- 7. 動態規劃的題目練習(leetcode)——有生之年系列,來源於潮汐的知乎專欄
- 8. 優化問題
- 9. 總結:
- 10. 參考資料:
1. 動態規劃究竟是要幹什麼
動態規劃就是暴力解決問題(窮舉法),但是動態規劃是練過軍體拳的、一拳一個嚶嚶怪(窮舉的優化),一般的窮舉法是亂拳打老師傅、一大通王八拳完事(直接暴力)。動態規劃的核心思想就是用一個備忘錄將子問題的最優解全部疊加起來,將一個個小的問題進行疊加變成大問題,疊到最後就是整個問題的最優解。說的通俗一點就是:大事化小,小事化了,一般而言,他解決的求最值問題具有以下三個特徵:
- 暴力窮舉的時候,存在重疊子問題:通常許多子問題非常相似,爲此動態規劃法試圖僅僅解決每個子問題一次,具有天然剪枝的功能,從而減少計算量:一旦某個給定子問題的解已經算出,則將其記憶化存儲,以便下次需要同一個子問題解之時直接查表。這種做法在重複子問題的數目關於輸入的規模呈指數增長時特別有用。比較人話一點:上一個解決的子問題,可以用作下一個子問題的參考答案(又不是西方國家抗疫,抄都不會抄)。有的直接就抄,所以用一個備忘錄來記錄下解決過的東西,再遇到直接抄就完事了,從而減低了時間複雜度。
- 子問題存在最優子結構:每一個小的部分都可以找到各自在這個階段的最優解,最後每一個小的子問題的最優解疊加起來起到質變得到最終解。
- 存在狀態轉移方程:上一個子問題可以通過某種方式(加上一些東西)變成下一個子問題的答案。
如果將已經學過的東西進行縱向對比的話,我覺得所謂的動態規劃特別像高中數學的送(na)分(ming)題——等差數列裏面通過A1和d確定An的值
其實動態規劃的代碼並不長,正所謂濃縮的就是精華,但是他的狀態轉移方程構建和重疊子問題的消除就很難,沒什麼關係,下面會有三個思考步驟,幫助構建狀態轉移方程。在這裏,我不會去弄教科書裏面什麼自頂向下的辦法之類的,那種花裏胡哨的,我是怎麼實用怎麼來,在熟悉了動態規劃的思考方式之後,我會再回到概念上加深理解。
2. 動態規劃的思考步驟
其實leetcode題目medium難度已經是面試正常題目的難度了,hard難度的話都是面試中比較難的題目,所以medium難度多做一道作爲訓練,hard只有一道,想多做的話建議自己看後面的有生之年系列去練習
其實動態規劃的三個步驟和“等差數列裏面通過A1和d確定An的值”的求解思路,區別不大,思想都是找到“d”和“a0”,確定ak。(下標不好打,看間隔)
- 定義子問題的最優解(ak-2與ak-1)的含義(我是誰),動態規劃畢竟不是等差數列,ak-2與ak-1並不是一個具體的數字,在解題中需要確定ak-2與ak-1是什麼形式,是一個最長距離還是一個true or false 的flag,那麼纔好放到備忘錄中消除重疊子問題,也方便確定最後解的形式。
- 找到“ak+1 = ak + d”這個方程式,確定子問題之間的遞推關係(我要到哪裏去),放在等差數列中就是找d,這就需要一點數學知識,穩住,這是最難的一部分了,但是穩住別慌,問題不大在涉及到遞歸的狀態轉移方程:有一個用於遞歸的思維框架(這個我是看Github項目《labuladong/fucking-algorithm》知道的,在解決確實好用):
明確「狀態」 -> 定義 dp 數組/函數的含義 -> 明確「選擇」-> 明確 base case。 - 找到初始值a0(我從哪裏來),由零開始一層層往上走。
有了初始值,並且有了數組元素之間的關係式,那麼我們就可以得到 dp[n] 的值了,而 dp[n] 的含義是由你來定義的,你想求什麼,就定義它是什麼,這樣,這道題也就解出來了。
那麼好,我們用例題進行講解,因爲確實幹寫幹讀實在太過於枯燥了。
3. 最大子序列和(入門難度,瞭解思維方式)
來源:leetcode《53. 最大子序和》
3.1 題目描述
給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。
很巧的是這題有視頻講解,我可能講的不太好,可以看這個視頻:
官方題解,那裏就有,一點開就可以了,b站鏈接:53. 最大子序和 Maximum Subarray 【LeetCode 力扣官方題解】
3.2 那麼好,這裏用我的三個步驟進行分析
來,拿出我們的小紙紙,對着例題來進行分析。
1.定義子問題的最優解(ak-2與ak-1)的含義。(我是誰)
題目問的是:給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。那麼在這裏直接找第k個數所對應的最大子序列之和不就完事了嗎,用例子算一下先,嗯,怎麼:
4 -1 2 1 -5找到的最大子序列是4 -1 2 1 ,4 -1 2 1 -5 4 找的還是 4 -1 2 1
這樣子問題就重複了,想一想先,爲什麼重複,重複就是因爲我停下來之後還使勁往前蹦躂,做別人的事情,那不是累死自己嗎,那如果我直接在4 -1 2 1 -5 4的4那裏剎住車,我也不往前面跳,我就踏踏實實的管住我門前的三畝地(也就是隻管2 1 -5 4又或者是-1 2 1 -5 4,而不是4 -1 2 1)那不就ok了,如果每個數都管好自己的三畝地,並且把這個最大值記錄下來,就不需要再往前跳(重複計算),我就可以少走一點路,趴在牀上刷手機不妙嗎,最後我再把這些記錄來個全部比較大小不就完事了嗎
所以:找到當前位置第k個數結尾所對應的最大連續子序列之和,最後直接返回a0,a1……an之間最大值ak。
比較動態的過程可以看上面視頻。
2.找到“ak+1 = ak+ d”這個方程式,確定子問題之間的遞推關係。(我要到哪裏去)
來看一下ak與ak-1
ak = 當前位置第k個數結尾所對應的最大連續子序列之和(nums[i]……nums[k])
ak+1 = 當前位置第k個數結尾所對應的最大連續子序列之和(nums[i]……nums[k],nums[k+1])
那麼可以看到,兩者之間的差別就在多了個nums[k+1],而ak的設定是當前位置第k個數結尾所對應的最大連續子序列之和,那麼就比較num[k+1]單獨成爲一段與ak+nums[k+1]連接成段之間的大小,選取兩者之間最大的那一個作爲a[k]
所以得出狀態轉移方程:a[k+1] = max(ak + nums[k+1],nums[k+1])。
3.找到初始值a0,由零開始一層層往上走。(我從哪裏來)
這個就簡單了,初始值a0 = nums[0]
3.3 那麼好,經過三個思維步驟確定了狀態轉移方程,就可以直接打僞代碼了
1. MAX-SUB-ARRAY(nums)
2. maxAnswer = nums[0] //k = 0的情況
3.
4. //當前位置第k個數結尾所對應的最大連續子序列之和(nums[0]……nums[k])
5. pre = 0
6.
7. for i = 0 to the length of the nums,i++
8. pre = max(pre+nums[i],nums[i])
9. maxAnswer = max(maxAnswer,pre)
10. //直接比較可以減低空間複雜度,從O(N)(額外數組保存當前的最大值)降低到O(1)
11. end for
12. return maxAnswer
3.4 做完了,進行入門題目的總結
步驟一:在當前可能子情況不太明確的時候,可以把問題所問的整體改成局部(給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。變成第k個數所對應的最大子序列之和),然後用例子進行分析,做個人肉計算機,看一下算到哪裏的時候會重複,發現重複的點就可以開始思考改正的方式,最後確定ak的形式。
步驟二:確定了形式,通過ak+1和ak之間形式的比較,以此確定兩者之間的關係,當然對着例子是最方便的
步驟三:這個就看最小的時候是什麼情況,沒什麼訣竅。
4. 最長迴文子串(medium難度,熟練一下)
leetcode《5. 最長迴文子串》
再做一道題,去熟悉一下,這裏我不會講的這麼細,讓讀者自行思考一下(其實,就是懶得寫)
4.1 題目描述
給定一個字符串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度爲 1000。
4.2 按照三個步驟進行分析
1.定義子問題的最優解(ak-2與ak-1)的含義。(我是誰)
這個就比較簡單是能夠一眼看出來的,ak = s[i]……s[j]是否爲迴文字符串的標誌,是迴文字符串則爲true否則爲false,如果最後a[i,j]到最後還是true,而且長度比較長,那就返回他的長度就完事了。那麼考慮到他存儲的是一個子字符串s[i]……s[j],所以這裏應該用二維數組來保留 i(最左邊的範圍)與 j(最右邊的範圍):(在設置備忘錄的元素含義的時候,也要考慮備忘錄存儲格式問題)
ak[ i ][ j ] = s[i]……s[j]是否爲迴文字符串的標誌,是迴文字符串則爲true否則爲false,如果最後a[i,j]到最後還是true,而且長度比較長,那就返回他就完事了。
2.找到“ak+1 = ak+ d”這個方程式,確定子問題之間的遞推關係。(我要到哪裏去)
這個問題嘛,畫個圖就🆗了
那很明顯了,如果新加入的s[ i ][ j ]與a[ i ][ j ]都是true那就直接完事了,有一個是false那就歇逼。
換到方程式裏面就是:
3.找到初始值a0,res = “ “爲空字符串(我從哪裏來)
4.3 好,可以直接寫出僞代碼
1. LONGEST-PALINDOMIC-SUBSTRING(s)
2. res = " "
3. n = the lenngth of the s
4. dp = [][]
5. for i = n - 1 to i = 0
6. for j = i to n
7. dp[i][j] = ( s[j] == s[j] ) && ( j - i < 2 ||dp[i + 1][j - 1]) //判斷是否爲迴文字符串方程
8. if dp[i][j] == true && j - i + 1 > the length of the res
9. res = the substring from s[i] to s[j + 1] //比現在長就加進去
10. end if
11. end for
12. end for
13. return res
4.4 該題總結
要注意備忘錄的存儲格式問題,不僅僅只有一維數組。
5. 知道了刷題的套路,再回頭深入理解動態規劃的本質,爲什麼動態規劃會這麼6
那麼好,到這裏我相信都已經對動態規劃怎麼樣思考,已經有點概念,對於怎麼寫應該有點數,但有了刷題的外功,沒點內功還是花拳繡腿幾下歇逼,因此回到動態規劃的概念上,並且解決一個連在一起的問題:
什麼是動態規劃(Dynamic Programming)?動態規劃的意義是什麼?
在這裏我建議直接看知乎用戶王勐對問題的回答《什麼是動態規劃(Dynamic Programming)?動態規劃的意義是什麼?》,這個是比較直覺性的,比較嚴謹的帶數學的看知乎用戶覃含章對問題的回答《什麼是動態規劃(Dynamic Programming)?動態規劃的意義是什麼?》
該文章對於爲什麼需要動態規劃,可以說是非常清楚,去看一下,再看下面的題目,你纔能有更加深刻的理解。
精華摘要:
所謂的空間複雜度就是爲了支持你的計算所必需存儲的狀態最多有多少,所謂時間複雜度就是從初始狀態到達最終狀態中間需要多少步!
對於狀態轉移方程,而言每求一個新數字只需要之前的兩個狀態。所以同一個時刻,最多只需要保存兩個狀態,空間複雜度就是常數;每計算一個新狀態所需要的時間也是常數且狀態是線性遞增的,所以時間複雜度也是線性的。
動態規劃對於貪心、遞推、搜索之間有什麼區別?
一個問題是該用遞推、貪心、搜索還是動態規劃,完全是由這個問題本身階段間狀態的轉移方式決定的!每個階段只有一個狀態->遞推;每個階段的最優狀態都是由上一個階段的最優狀態得到的->貪心;每個階段的最優狀態是由之前所有階段的狀態的組合得到的->搜索;每個階段的最優狀態可以從之前某個階段的某個或某些狀態直接得到而不管之前這個狀態是如何得到的->動態規劃。
6. 高樓砸雞蛋問題(hard難度,帶着狀態變化思考問題)
leetcode《887. 雞蛋掉落》
那麼好,看完了前面的easy難度,是時候將難度進階到面試中、考研上機的較難難度了,不要慌張,我會儘可能說明白,有視頻不慌,·一道題不夠,下面有生之年系列走起。
6.1 題目講述
將獲得 K 個雞蛋,並可以使用一棟從 1 到 N 共有 N 層樓的建築。
每個蛋的功能都是一樣的,如果一個蛋碎了,你就不能再把它掉下去。(也就是一個雞蛋沒有碎,你可以繼續往下丟,話說這算不算高空擲物,要去b站@一下羅老師先,看一下能不能把這個法外狂徒張三按在地上錘一頓)
你知道存在樓層 F,滿足 0 <= F <= N 任何從高於 F 的樓層落下的雞蛋都會碎,從 F 樓層或比它低的樓層落下的雞蛋都不會破。
每次移動,你可以取一個雞蛋(如果你有完整的雞蛋)並把它從任一樓層 X 扔下(滿足 1 <= X <= N)。
你的目標是確切地知道 F 的值是多少。
無論 F 的初始值如何,你確定 F 的值的最小移動次數是多少?
吐槽一下,官方題解那一大堆文字(一堆公式,數學論文的既視感)屬實沒看懂,看視頻倒是看懂了。
b站上有個很高播放量的視頻也講解了這個題目可以去看一下:
b站視頻李永樂老師官方《復工復產找工作?先來看看這道面試題:雙蛋問題》
leetcode官方也有視頻(我沒看懂,但是我的最後一個優化的代碼是參考了官方的代碼):
6.2 在正式解題前,吹一波水,穩住
這道題也是個老面試題了,不過說個實在話,動態規劃能玩出個什麼花樣,無非就是一個dp方程,加上一個備忘錄的利用,就是一個用空間換時間的優化版本窮舉。奧裏給
該題題解參考了:leetcode的 887. 雞蛋掉落題目 labuladong的題解《題目理解 + 基本解法 + 進階解法》
6.3 等一下,我這題直接用二分搜索不就完事了嘛
不行啊,雞蛋夠肯定可以往死裏砸,但是現在問題是雞蛋不夠啊,你看如果你只有一個雞蛋八層樓,你直接從4樓丟了下去,運氣不好直接碎了就歇逼了,這種情況肯定也只能從一樓一路走上去。懂我意思吧,雞蛋不夠要省着點花。
6.4 三個思考步驟
1.定義子問題的最優解(ak-2與ak-1)的含義(我是誰),在這裏由於有兩個狀態一個是樓層N一個是雞蛋的數量K,因此用一個二維數組a[ i ][ j ]存儲當前i個雞蛋和j個樓層高度的時候,最少丟雞蛋的次數。
2.找到“ak+1 = ak + d”這個方程式,確定子問題之間的遞推關係(我要到哪裏去),這時候考慮到兩種情況,
- 雞蛋碎了,那麼 雞蛋數目K應該減一,同時也要從當前樓層j往下移動。
- 如果 雞蛋沒有碎,那就往上移動,雞蛋數目保持不變
因爲我們要求的是 最壞情況下丟雞蛋的次數,也就是意味着,雞蛋在第 j 層沒有炸,取得是往上走和往下走之間值最大的那一個。所以應該選擇:
max(dp(K - 1,j - 1),dp(K,N - j))來獲取在當前第j層最壞情況下扔雞蛋的數目, 等一下,還有個最少次數,那就設立一個res去保留之前樓層所丟下雞蛋的最小次數並且與當前的進行比較,看一下誰比較少,也就是說:
res = min(res,max(dp(K,N - i),dp(K - 1,i - 1))+ 1)
由於往上和往下走的探索方式一樣,探索到最後都是把雞蛋砸完或者是雞蛋往下走樓層到底了,所以在這裏採用遞歸的方式。
7.dp(K,N)
6. //基準情況
7. if K == 1
8. return N //只剩下一個雞蛋,玩個錘子,最壞情況肯定是該樓層剛剛好
9. if N == 0
10. return 0 //樓層丟完了,就可以結束子情況
11.
12. if (K,N) in memeo
13. return memo[(K,N)] //如果子情況之前已經記錄過,那就直接跳過
14.
15. for i to the N + 1 //窮舉所有可能出現的情況,注意要從地面0開始往上算
16. res = min(res,max(dp(K,N - i),dp(K - 1,i - 1))+ 1)
17. end for
18.
19. memo[(K,N)] = res //放入備忘錄中
20. return res //返回在當前N與K的數量時候,所需要的最小移動次數
3.初始值,這題沒有初始值,每一個狀態都要自己進行探索,如果硬說的話,基準情況可以作爲初始值
6.5 構建僞代碼
//方便理解版本
1.SUPER-EGG-DROP(K,N)
3. return dp(K,N)
4.
4.memo = {} //把備忘錄,做成全局變量,用來記錄每個不同時期子情況的值
5.
6.//動態規劃方程
7.dp(K,N)
6. //基準情況
7. if K == 1
8. return N //只剩下一個雞蛋,玩個錘子,從一樓往上扔吧
9. if N == 0
10. return 0 //樓層丟完了,就可以結束子情況
11.
12. if (K,N) in memeo
13. return memo[(K,N)] //如果子情況之前已經記錄過,那就直接跳過
14.
15. for i to the N + 1 //窮舉所有可能出現的情況,注意要從地面0開始往上算
16. res = min(res,max(dp(K,N - i),dp(K - 1,i - 1))+ 1)
17. end for
18.
19. memo[(K,N)] = res //放入備忘錄中
20. return res //返回在當前N與K的數量時候,所需要的最小移動次數
6.6 其實呢,這題還是有辦法二分的
//加上二分的版本
1.SUPER-EGG-DROP(K,N)
2. return dp(K,N)
3.
4.memo = {} //把備忘錄,做成全局變量,用來記錄每個不同時期子情況的值
5.
6.//動態規劃方程
7.dp(K,N)
8. //基準情況
9. if K == 1
10. return N //只剩下一個雞蛋,玩個錘子,從一樓往上扔吧
11. if N == 0
12. return 0 //樓層丟完了,就可以結束子情況
13.
14. //注意開始和前面的開始不一樣
15. low = 1,high = N //開始往中間夾
16. while low <= high
17. mid = (low + high)/2
18. the number of broken = dp(K - 1,mid - 1) //碎了
19. the number of not broken = dp(K,N - mid) //沒碎
20.
21. // res = min(res,max(the number of broken,the number of not broken) + 1)
22. if the number of broken > the number of no broken then
23. high = mid - 1
24. res = min(res,the number of broken + 1)
25. else
26. low = mid + 1
27. res = min(res,the number of not broken + 1)
28. end if
29. end while
30. memo[(K,N)] = res //放入備忘錄中
31. return res //返回在當前N與K的數量時候,所需要的最小移動次數
6.7 來一波反向操作,看一下你懂了沒
這一部分來源:Ikaruga的題解《【雞蛋掉落】5行代碼,從求扔幾次變爲求測多少層樓 =附面試經歷=》
注意來源,這裏不用做商用,只是作爲學習筆記分享,本文章沒有任何收益,如果侵權該部分立即刪除
7. 動態規劃的題目練習(leetcode)——有生之年系列,來源於潮汐的知乎專欄
注意這一部分,博主沒有親自收集,博主也是看別人的文章過來的,如果可以去幫別人點個贊吧。如果造成侵權(該部分並非商用,只是用作學習筆記),立馬刪除,不多bb。
潮汐的知乎專欄《[力扣] DP問題分類彙總》
我也準備按照它的題目分類去做一遍,大佬nb,大佬的分類是真好。
能做就做吧,因爲確實:
7.1 線性DP
最經典單串:《300. 最長上升子序列》
最經典的雙串:《1143. 最長公共子序列》
經典問題:
leetcode題目《120. 三角形最小路徑和》、《53. 最大子序和》、《152. 乘積最大子數組》、《887. 雞蛋掉落 (DP+二分)》、《354. 俄羅斯套娃信封問題 (隱晦的LIS)》
打家劫舍系列: (打家劫舍3 是樹形DP)
《198. 打家劫舍》、《213. 打家劫舍 II》
股票系列:
《121. 買賣股票的最佳時機》、《122. 買賣股票的最佳時機 II》、《123. 買賣股票的最佳時機 III》、《188. 買賣股票的最佳時機 IV》、《309. 最佳買賣股票時機含冷凍期》、《714. 買賣股票的最佳時機含手續費》
字符串匹配系列
《72. 編輯距離》、《44. 通配符匹配》、《10. 正則表達式匹配》
7.2 區間DP
《516. 最長迴文子序列》、《730. 統計不同迴文子字符串》、《1039. 多邊形三角剖分的最低得分》、《664. 奇怪的打印機》、《312. 戳氣球》
7.3 揹包DP
《416. 分割等和子集 (01揹包-要求恰好取到揹包容量)》、《494. 目標和 (01揹包-求方案數)》、《322. 零錢兌換 (完全揹包)》、《518. 零錢兌換 II (完全揹包-求方案數)》、《474. 一和零 (二維費用揹包)》
7.4 樹形DP
《124. 二叉樹中的最大路徑和》、《1245. 樹的直徑 (鄰接表上的樹形DP)》、《543. 二叉樹的直徑》、《333. 最大 BST 子樹》、《337. 打家劫舍 III》
7.5 狀態壓縮DP
《464. 我能贏嗎》、《526. 優美的排列》、《935. 騎士撥號器》、《1349. 參加考試的最大學生數》
7.6 數位DP
《233. 數字 1 的個數》、《902. 最大爲 N 的數字組合》、《1015. 可被 K 整除的最小整數》
7.7 計數型DP
計數型DP都可以以組合數學的方法寫出組合數,然後dp求組合數
《62. 不同路徑》、《63. 不同路徑 II》、《96. 不同的二叉搜索樹 (卡特蘭數)》、《1259. 不相交的握手 (盧卡斯定理求大組合數模質數)》
7.8 遞推型DP
所有線性遞推關係都可以用矩陣快速冪做,可以O(logN),最典型是斐波那契數列
《70. 爬樓梯》、《509. 斐波那契數》、《935. 騎士撥號器》、《957. N 天后的牢房》、《1137. 第 N 個泰波那契數》
7.9 概率型DP
求概率,求數學期望
《808. 分湯》、《837. 新21點》
7.10 博弈型DP
策梅洛定理,SG定理,minimax
《293. 翻轉游戲》、《294. 翻轉游戲 II》、《292. Nim 遊戲》、《877. 石子游戲》、《1140. 石子游戲 II》、《348. 判定井字棋勝負》、《794. 有效的井字遊戲》、《1275. 找出井字棋的獲勝者》
7.11 記憶化搜索
本質是 dfs + 記憶化,用在狀態的轉移方向不確定的情況
《329. 矩陣中的最長遞增路徑、《576. 出界的路徑數》
8. 優化問題
其實我認爲優化改進集中在初始化定義哪裏,確定的好就ok確定不好就歇逼,和我第一題一樣,其實真的沒有什麼訣竅,做多一點題,吃多一點虧,再做題才能看到,因爲你腦子裏面根本沒有優化的方向的話,再怎麼分析也是歇逼
9. 總結:
本文還是實用解題爲主,因此總結了動態規劃的適用場景和思考步驟
9.1 動態規劃的適用場景
- 子問題的答案依賴於問題的規模,也就是子問題的所有答案構成了一個數列
- 大規模問題的答案可以由小規模問題的答案遞推得到
9.2 動態規劃的思考步驟
動態規劃的三個步驟可以概括成三個步驟:我是誰,我要去哪裏,我從哪裏來。
- 定義子問題的最優解(ak-2與ak-1)的含義(我是誰)
- 找到“ak+1 = ak + d”這個方程式,確定子問題之間的遞推關係(我要去哪裏)
- 找到初始值a0(我從哪裏來),由零開始一層層往上走。
有了初始值,並且有了數組元素之間的關係式,那麼我們就可以得到 dp[n] 的值了,而 dp[n] 的含義是由你來定義的,你想求什麼,就定義它是什麼,這樣,這道題也就解出來了。
10. 參考資料:
- leetcode《887. 雞蛋掉落》leetcode的 887. 雞蛋掉落題目 labuladong的題解《題目理解 + 基本解法 + 進階解法》、 Ikaruga的題解《【雞蛋掉落】5行代碼,從求扔幾次變爲求測多少層樓 =附面試經歷=》
- leetcode《53. 最大子序和》,以及靈魂畫手的題解《畫解算法:53. 最大子序和》
- leetcode《5. 最長迴文子串》,以及windliang的題解《詳細通俗的思路分析,多解法》
- CSDN博主mmc2015的《非常好的動態規劃總結,DP總結》、CSDN博主BS有前途的《經典中的經典算法:動態規劃(詳細解釋,從入門到實踐,逐步講解)》、CSDN博主HankingHu的《算法-動態規劃 Dynamic Programming–從菜鳥到老鳥》
- 帥地的知乎專欄《告別動態規劃,連刷 40 道題,我總結了這些套路,看不懂你打我(萬字長文)》、 潮汐的知乎專欄《[力扣] DP問題分類彙總》
- 博客園博主英雄哪裏出來的《夜深人靜寫算法(二) - 動態規劃》
- Github項目《labuladong/fucking-algorithm》,b站leetcode官方的《887. 雞蛋掉落 Super Egg Drop 【LeetCode 力扣官方題解】》
- b站視頻李永樂老師官方《復工復產找工作?先來看看這道面試題:雙蛋問題》
- 我自己的博客《回溯算法以及剪枝技巧(內附通用構建模板,文末有C++、JAVA、Python的實現)》
- 知乎用戶王勐對問題的回答《什麼是動態規劃(Dynamic Programming)?動態規劃的意義是什麼?》,知乎用戶zhen tan對問題的回答《如何理解動態規劃?》,知乎用戶覃含章對問題的回答《什麼是動態規劃(Dynamic Programming)?動態規劃的意義是什麼?》,知乎用戶阮行止在該問題的回答
- 算法導論P202-P236