為什麼你不會動態規劃?

2021-10-08 12:45:35 字數 4509 閱讀 2741

動態規劃(dynamic programming)是刷題中最常見也最重要的乙個類別,在研發崗位面試中往往佔據著重要地位。對於刷題,要寫在前面的是肯定要通過大量的練習和自身體會去把握每種題型的思路,這是大前提。不過動態規劃(以下用dp代替)屬於沒有公式的題型,這種題的特點是可能你花了很長時間去練習,卻只能會做之前做過的題;dp的意義就像高考中最後一道大題,成為拉開差距的關鍵。

那我們不妨用高考數學的思路去理解dp,解題邏輯抽象出來公式化,再加上一定的解題經驗,至少做到拿到題後迅速找到思路。按圖索驥總比毫無意義的體會分類求解強。這篇文章先介紹dp概念化的東西幫助你理解dp概念和核心,然後通過介紹的概念提出一般公式再通過公式幫助你在實戰中攻略dp題目。

什麼是動態規劃?

動態規劃指把多階段過程轉化為一系列單階段問題,然後利用各階段之間的關係,逐個求解。

這裡的各階段的關係我們可以使用狀態轉移方程表示,相當於你要得到最後的乙個結果只需要利用各個階段的關係逐步推倒就可以實現。聽上去是不是很有道理又很熟悉,比較像高中學習的數學歸納法?

在數學歸納法中,最簡單和常見的數學歸納法是證明當n等於任意乙個自然數時某命題成立。證明分下面兩步:

證明當n = 1時命題成立。

證明如果在n = k時命題成立,那麼可以推導出在n = k+1時命題也成立。(k代表任意自然數)

其實也是一種利用各階段之間的關係逐個求解證明最終命題的過程。關鍵在於怎麼提取這個子關係並讓他服務於最後的結果。

提出總體思路之前我們再來體會下dp思想的兩個特性來輔助我們理解和設計。

最優子結構

最優子結構指的是後面階段的狀態可以通過前面狀態推導出來,而每乙個階段的最優決策序列的子串行也是最優的。這樣才能保證我們可以利用各階段的關係去求最終解。

拿leetcode上的爬樓梯這道題做例子:

假設你正在爬樓梯。需要 n 階你才能到達樓頂。

每次你可以爬 1 或 2 個台階。你有多少種不同的方法可以爬到樓頂呢?

注意:給定 n 是乙個正整數。

這道題其實本質是斐波拉契數列,但我們用dp思想來尋找這道題的最優子結構你會發現:

通過條件我們知道,我登上第n階台階其實只能通過登上第n-1階或者第n-2階台階時實現,而且可以確定這是登上第n階的最優子結構(可以推導出來並且沒有其他最優解法),這種確定最優子結構的思想是需要你靠刷題或培養訓練出來的,也是解開dp問題的關鍵。

無後效性

對於每個子串行再做最優決策會產生新的最優決策(子)序列,如果某個決策只受當前最優決策子串行的影響,而不受當前決策可能產生的新的最優決策子串行的影響,則可以理解這個最優決策具有無後效性。

通俗點說就是,無論之前的狀態是怎麼拿到的,之前的狀態也不會影響之後的狀態的產生。如果仍然拿爬樓梯這道題舉例就是:

可以確定我登上第n階台階之後,結果已經確定下來了,當我想再計算n+1的值的時候,不需要去回溯考慮我n的值的計算過程。

什麼樣的題會用動態規劃?

我的理解是先熟讀三遍定義,再理解下第一節寫的內容,如果你能確定具有無後效性的子結構並可推導成最後結果,那麼這道題就可以使用dp。

遇到動態規劃怎麼辦(這裡給出的是一般性的解決方案,可能並不是最優解,但你可以依次為基礎提公升)

我一般會用dp或二維dp儲存需要快取記錄的子結構中間值,n表示最終要求的終值,i表示所求序列中的中間值

1. 思考如何定義dp[i]

這步其實不難,但還是需要有乙個很好的解題感,首先你要清晰地定義乙個子結構,比如爬樓梯中dp[i]可以表示為我爬到第i階台階的方法數。

2. 定義dp[0]

這一步是在第一步建立起的基礎上,順理成章得出的初始值,很多問題其實要考慮到邊界問題,考慮dp[0]既可以幫我們初步解決邊界問題,又可以讓我們確定解決問題的大致區域。比如爬樓梯中dp[0]考慮到邊界問題,可以表示為爬第一級樓梯時的只有一種方法,然後回溯修改dp[i]的定義為爬到第i+1級台階所需方法數。

3. 定義dp[i]之間的關係

這裡應該是最難考慮的狀態轉移方程,可以簡化的是你可以先不考慮邊界問題,只考慮如果在整個序列的中間,會怎麼推導。

比如爬樓梯中會結合初始值定義想到dp[i]=dp[i-1]+dp[i-2],即登上第i階是登上第i-1階的方法數加上第i-2階的方法數。

4. 求最終值與dp[n]的關係

經過邊界和狀態轉移方程的確定,再考慮定義後的dp[n]和我想求得的答案之間的關係,在爬樓梯例題這裡其實是答案就是dp[n-1](根據初始值確定不同變化)

總結下:

一般性解決dp思路問題

寫出**:

func climbstairs(n int) int 

dp[0] = 1

if n == 1

dp[1] = 2

if n == 2

for i := 2; i < n; i++

return dp[n-1]

}

這裡選了兩道題,一道是二維dp代表最小路徑和,另一道是位元組的面試題查詢連續6。當然還有其他的dp題比如最低票價等需要在狀態轉移方程上下功夫的還需要練習,不過這裡提出的是general的方法,應該適應於全部的dp題解。

1.  最小路徑和

給定乙個包含非負整數的 m x n 網格,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和為最小。

說明:每次只能向下或者向右移動一步。

示例:輸入:

[[1,3,1],

[1,5,1],

[4,2,1]

]輸出: 7

解釋: 因為路徑 1→3→1→1→1 的總和最小。

按圖索驥:

1. 確定dp[i]的定義:

這裡明顯要為每乙個區域設定dp快取記錄值,所以定義dp[i][j]為走到位置(i,j)時路徑最小值

2. 確定dp[0]:

dp[0][0]=1

3. 定義dp[i]之間的關係

我們可以知道走到(i,j)的結果可能由從dp[i-1][j]得到,也可能由dp[i][j-1]得到,滿足無後效性,而dp[i][j]的值是取可選值中最小的乙個與自己所在的位置數值相加,即dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]

4. 最終值與dp[n]的關係

結果就是求dp[m-1][n-1]

綜上可碼**:

func minpathsum(grid int) int 

dp := make(int, l)

for i, arr := range grid

dp[0][0] = grid[0][0]

for i := 0; i < l; i++ else if j == 0 && i != 0 else if i !=0 && j != 0

} }return dp[l-1][len(dp[l-1])-1]

}func min(a, b int) int

return a

}

這裡唯一附加的工作是呼叫狀態轉移函式時確定邊界值,總體邏輯還是很清晰的,屬於典型的dp題解,然後在這個基礎上可以再優化整個題解使得時間空間複雜度提公升。

2. 整數中連續6的數量

給定乙個正整數n,判定1到n位正整數中含有連續6的數有多少個

這道題也是dp規劃,但需要多動下腦筋,不是正常leetcode刷到的特徵性明顯的題。

首先確定題意,連續的6指的是66,666等等這種數字,比如n=1是即1位正整數不可能含有連續的6;

n=2時1-99之間只有乙個數字含連續的6 就是66.

n=3時要怎麼想呢?和n=2有關係麼?能否利用n=2求出n=3的解?

按圖索驥:

1. 定義dp[i]

dp[i]代表第i位數時含有連續6的數量

2. dp[0]的值

這裡一位數沒有連續的6,所以要同時修改第一步的定義為dp[i]代表第i+1位數時含有連續6的數量

同時dp[0]=0代表一位數,dp[1]=1代表兩位數

3. dp[i]的關係

以i=2為例,三位數中可以看成是兩位數+一位數形式和一位數+兩位數這兩種形式,兩位數中我們已經求得值dp[1]=1

i=2時 可以和兩位數搭配的一位數有10種,總共兩種形式中,有一種是重合的情況,即666,所以dp[2]=dp[1]*(10*2-1)=19

以此類推可以知道dp[i]只與dp[i-1]有關係,且無後效行,所以dp[i]=dp[i-1]*19

4. 求得結果與dp[n]關係

dp[n-1]為所求結果

**不複雜,故省略

你為什麼總是學不會設計模式

設計模式 最熟悉的陌生人。很多人應該都有這種感受,早就知道設計模式,也能隨口說出幾種,但是不知道每種是怎麼回事。或者說只知道工廠模式等幾個常用,簡單的。估計那也是靠記憶,而不是真正的理解。我也有這種親身體會,在好幾年之前就知道設計模式。之前也一直抽時間學,但總是學了就忘。而且學習過程很枯燥,完全是背...

你為什麼總是學不會設計模式?

設計模式 最熟悉的陌生人。很多人應該都有這種感受,早就知道設計模式,也能隨口說出幾種,但是不知道每種是怎麼回事。或者說只知道工廠模式等幾個常用,簡單的。估計那也是靠記憶,而不是真正的理解。我也有這種親身體會,在好幾年之前就知道設計模式。之前也一直抽時間學,但總是學了就忘。而且學習過程很枯燥,完全是背...

教你徹底學會動態規劃 高階篇

話不多說,來看如下例題,也是在動態規劃裡面遇到過的最頻繁的乙個題,本題依然來自於北大poj 最長公共子串行 poj1458 給出兩個字串,求出這樣的乙個最長的公共子串行的長度 子串行中的每個字元都能在兩個原串中找到,而且每個字元的先後順序和原串中的先後順序一致。sample input abcfbc...