動態規劃
動態規劃,英文:Dynamic Programming,簡稱DP,如果某一問題有很多重疊子問題,使用動態規劃是最有效的。
所以動態規劃中每一個狀態一定是由上一個狀態推匯出來的,這一點就區分於貪心,貪心沒有狀態推導,而是從區域性直接選最優的,
我在這裡舉一個典型的動態規劃的問題——揹包問題:
例如:有N件物品和一個最多能背重量為W 的揹包。第i件物品的重量是weight[i],得到的價值是value[i] 。每件物品只能用一次,求解將哪些物品裝入揹包裡物品價值總和最大。
動態規劃中dp[j]是由dp[j-weight[i]]推匯出來的,然後取max(dp[j], dp[j - weight[i]] + value[i])。
但如果是貪心呢,每次拿物品選一個最大的或者最小的就完事了,和上一個狀態沒有關係。
所以貪心解決不了動態規劃的問題。
而且很多講解動態規劃的文章都會講最優子結構啊和重疊子問題啊這些,這些東西都是教科書的上定義,晦澀難懂而且不實用。
大家知道動規是由前一個狀態推匯出來的,而貪心是區域性直接選最優的,對於刷題來說就夠用了。
上述提到的揹包問題,後序會詳細講解。
動態規劃的解題步驟
做動規題目的時候,很多同學會陷入一個誤區,就是以為把狀態轉移公式背下來,照葫蘆畫瓢改改,就開始寫程式碼,甚至把題目AC之後,都不太清楚dp[i]表示的是什麼。
這就是一種朦朧的狀態,然後就把題給過了,遇到稍稍難一點的,可能直接就不會了,然後看題解,然後繼續照葫蘆畫瓢陷入這種惡性迴圈中。
狀態轉移公式(遞推公式)是很重要,但動規不僅僅只有遞推公式。
動態規劃五部曲
對於動態規劃問題,我將拆解為如下五步曲,這五步都搞清楚了,才能說把動態規劃真的掌握了!
- 確定狀態變數dp以及dp的含義
- 確定遞推公式
- dp陣列如何初始化
- 確定遍歷順序
- 舉例推導dp陣列
一些同學可能想為什麼要先確定遞推公式,然後在考慮初始化呢?
因為一些情況是遞推公式決定了dp陣列要如何初始化!
確定狀態變數dp
- 在動態規劃中,某一步的狀態dp,是一定由前面的狀態,或者後面的狀態所推出來的,所以說我們一定要理解dp陣列到底代表著什麼,它的含義十分重要,如果沒有搞懂一道題的dp陣列的含義是什麼,那麼我們就不能說掌握了這道題!
- 所以說狀態變數dp是非常重要的!
遞推公式
後面的講解中我都是圍繞著這五點來進行講解。
可能刷過動態規劃題目的同學可能都知道遞推公式的重要性,感覺確定了遞推公式這道題目就解出來了。
其實 確定遞推公式 僅僅是解題裡的一步而已!
一些同學知道遞推公式,但搞不清楚dp陣列應該如何初始化,或者正確的遍歷順序,以至於記下來公式,但寫的程式怎麼改都透過不了。
後序的講解的大家就會慢慢感受到這五步的重要性了。
動態規劃應該如何debug
相信動規的題目,很大部分同學都是這樣做的。
看一下題解,感覺看懂了,然後照葫蘆畫瓢,如果能正好畫對了,萬事大吉,一旦要是沒透過,就怎麼改都透過不了,對 dp陣列的初始化,遞推公式,遍歷順序,處於一種黑盒的理解狀態。
寫動規題目,程式碼出問題很正常!
找問題的最好方式就是把dp陣列列印出來,看看究竟是不是按照自己思路推導的!
一些同學對於dp的學習是黑盒的狀態,就是不清楚dp陣列的含義,不懂為什麼這麼初始化,遞推公式背下來了,遍歷順序靠習慣就是這麼寫的,然後一鼓作氣寫出程式碼,如果程式碼能透過萬事大吉,透過不了的話就憑感覺改一改。
這是一個很不好的習慣!
做動規的題目,寫程式碼之前一定要把狀態轉移在dp陣列的上具體情況模擬一遍,心中有數,確定最後推出的是想要的結果。
然後再寫程式碼,如果程式碼沒透過就列印dp陣列,看看是不是和自己預先推導的哪裡不一樣。
如果列印出來和自己預先模擬推導是一樣的,那麼就是自己的遞迴公式、初始化或者遍歷順序有問題了。
如果和自己預先模擬推導的不一樣,那麼就是程式碼實現細節有問題。
這樣才是一個完整的思考過程,而不是一旦程式碼出問題,就毫無頭緒的東改改西改改,最後過不了,或者說是稀裡糊塗的過了。
這也是我為什麼在動規五步曲裡強調推導dp陣列的重要性。
舉個例子哈:一些同學可能程式碼透過不了,會把程式碼拋到討論群裡問:我這裡程式碼都已經和題解一模一樣了,為什麼透過不了呢?
發出這樣的問題之前,其實可以自己先思考這三個問題:
- 這道題目我舉例推導狀態轉移公式了麼?
- 我列印dp陣列的日誌了麼?
- 列印出來了dp陣列和我想的一樣麼?
如果這靈魂三問自己都做到了,基本上這道題目也就解決了,或者更清晰的知道自己究竟是哪一點不明白,是狀態轉移不明白,還是實現程式碼不知道該怎麼寫,還是不理解遍歷dp陣列的順序。
總結
-
這一篇是動態規劃的整體概述,講解了什麼是動態規劃,動態規劃的解題步驟,以及如何debug。
-
動態規劃是一個很大的領域,今天這一篇講解的內容是整個動態規劃系列中都會使用到的一些理論基礎。
-
在後序講解中針對某一具體問題,還會講解其對應的理論基礎,例如揹包問題中的01揹包,leetcode上的題目都是01揹包的應用,而沒有純01揹包的問題,那麼就需要在把對應的理論知識講解一下。
-
我在後面也會更新很多關於動態規劃的經典題型以及解題方法。
註明:
- 本篇文章借鑑了作者程式碼隨想錄的思路以及部分文章原文,要想深入瞭解,可以移步原作者的文章:程式碼隨想錄 (programmercarl.com)