從例項中了解動態規劃的基本思想

2021-09-24 07:47:47 字數 4089 閱讀 1681

當時大學開的那麼多演算法課為啥一節都不好好聽講!

動態規劃,是一種解決棘手問題的方法,它將問題分成小問題,並從解決小問題作為起點,從而解決最終問題的一種方法。

看不明白沒關係,後面我們會從幾個例項中逐漸讓大家摸清規律。

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

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

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

輸入: 2

輸出: 2

解釋: 有兩種方法可以爬到樓頂。

1 階 + 1 階

2 階

輸入: 3

輸出: 3

解釋: 有三種方法可以爬到樓頂。

1 階 + 1 階 + 1 階

1 階 + 2 階

2 階 + 1 階

你可能會這麼想

走1階台階只有一種走法,但是走2階台階有兩種走法(如示例1),如果n是雙數,我們可以湊成m個2級台階,每個m都有兩種走法,如果n是單數,那麼我們可以湊成m個2級台階加上乙個1級台階,這樣就似乎於乙個排列組合題目了,但是開銷貌似比較大。

如何將整個問題化成乙個乙個的小問題

這個時候使用動態規劃就很有用,因為這個問題其實是由乙個很簡單的小問題組成的。 觀察這種小問題,簡單地我們可以採用首位或者中間態進行一次分析,比如我們從最終態進行分析:

走n階台階,最後一步必定是1步或者2步到達。

那麼n階台階的走法不就相當於最後走一步和最後走兩步的走法的總和嗎?換一種方式來說,我們取乙個中間態:如果總共有3級台階,3級台階的走法只會存在兩種大的可能:走了1階台階+走兩步、走了兩級台階+走一步,即3級台階的所有走法就是走了1階台階的走法加上走了2階台階的走法,而1階台階的走法只有一種,2階台階的走法有2種,所有3階台階的走法有3種,我們使用一種更通用的方式進行表達的話就是所謂的狀態轉換方程

有了這個公式,我們就可以使用迭代來完成整個過程,尋求到最終的ways[n]的值了,迭代的開始即我們已知的確定條件:一階台階只有一種走法:ways[1]=1、兩階台階有兩種走法:ways[2]=2,**如下:

實現**

public int climbstairs(int n) else if(n==2)

//避免使用0,即下標從1開始,更好理解

int ways=new int[n+1];

//賦值迭代初始條件

ways[1]=1;

ways[2]=2;

//利用狀態轉換方式進行迭代

for(int i=3;i<=n;i++)

return ways[n];

}複製**

基本流程

從上面的解決途徑我們可以發現基本流程是這樣的:

乙個機械人位於乙個 m x n 網格的左上角 (起始點在下圖中標記為「start」 )。

機械人每次只能向下或者向右移動一步。機械人試圖達到網格的右下角(在下圖中標記為「finish」)。

問總共有多少條不同的路徑?

例如,上圖是乙個7 x 3 的網格。有多少可能的路徑?

說明:m 和 n 的值均不超過 100。

輸入: m = 3, n = 2

輸出: 3

解釋:從左上角開始,總共有 3 條路徑可以到達右下角。

向右 -> 向右 -> 向下

向右 -> 向下 -> 向右

向下 -> 向右 -> 向右

輸入: m = 7, n = 3

輸出: 28

解決方法

相信沿用問題一的套路很多人已經知道該怎麼辦了,從乙個二維陣列的左上(0,0)走到右下(m,n)有多少種走法,且只能往右和往下走,那麼如果要走到(m,n),那麼我們的上一步只能是(m-1,n)或者(m,n-1),所以走到(m,n)的所有走法就是走到(m-1,n)的所有走法+走到(m,n-1)的所有走法,即可以得到狀態轉換方程:

但是,這個問題還有一些其他的問題限制需要我們考慮到,即走到兩側的時候,只會有乙個方向的走法,(上方只會有ways[m-1][n]乙個方式,左側只會有ways[m][n-1]乙個方式)即下圖:

我們需要對這兩種方式進行限制,在這裡我在外圍再擴充套件了一圈,將整個方格擴充套件為**(m+1)*(n+1)**的方格,來避開限制,當然也可以直接限制(後續會講到),但是將其所有的值都設定為0,即相當於設定了限制。

實現**

public static int uniquepaths(int m, int n) 

//邊上擴充套件一列,使其值為0

for(int j=0;j<=m;j++)

//設定初始值,起點走法為1,只能一步一步走

ways[1][1]=1;

for(int a=1;a<=m;a++)

//套用狀態轉換方程

ways[a][b]=ways[a][b-1]+ways[a-1][b];

} }return ways[m][n];

}複製**

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

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

輸入:[[1,3,1],

[1,5,1],

[4,2,1]

]輸出: 7

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

解決方法

這個問題與問題二及其相似,但是其涉及到乙個最優解的問題,現在每乙個點都有乙個類似權重的值,我們要使這個值最小,其實用問題二的想法,我們很快就能得到答案:走到(m,n)只能從(m-1,n)和(m,n-1)兩個地方走過來,那麼要保證(m,n)的權重最小,那麼我們只需要選擇走到(m-1,n)和(m,n-1)權重較小的那一邊即可,那麼我們就可以得到新的狀態轉移方程:

走到當前點的權重=走到前一步權重的較小值+當前點的權重並且該問題也有針對邊上元素的特殊處理

**

public static int minpathsum(int grid) {

//權重儲存陣列

int sum=new int[grid.length][grid[0].length];

//起點初始權重確定值

sum[0][0]=grid[0][0];

for(int i=0;i複製**

給定乙個三角形,找出自頂向下的最小路徑和。每一步只能移動到下一行中相鄰的結點上。

[[2],

[3,4],

[6,5,7],

[4,1,8,3]

]自頂向下的最小路徑和為 11(即,2 + 3 + 5 + 1 = 11)。

解決方法

這個問題可以理解為問題三的變種,但是他沒有乙個固定的終點,因為我們之前的方法都是從最後一步開始分析的,所以很多人也就對該問題無從下手了。但是其實我們也可以將最後一行的任何乙個元素作為終點,因為該問題起點確定,並且終點必定在最後一行。但是為了代表性,我們還是選取1或8為例子,如果最終達到1,需要上一排達到6或5。如果要達到5,那麼需要上一排達到3或4,所以我們由此可以得到該問題的狀態轉移方程:

這樣我們就可以根據問題三的模式找到達到最後一排所有可能終點(4,1,8,3)的最小權重,我們再從所有權重中選取最小值即可,該問題也有針對邊上元素的特殊處理

實現**

public static int minimumtotal(list> ********) {

//建立狀態儲存陣列

int sum=new int[********.size()][********.size()];

//起點確定,權重確定

sum[0][0]=********.get(0).get(0);

for(int i=0;i複製**

(參考《演算法**》)

動態規劃基本思想

動態規劃與貪心策略類似,將乙個問題的解決方案視為一系列決策的結果。不同的是,貪心演算法每採用一次貪心選擇便做出乙個不可撤回的決策,而在動態規劃中,還要考察每個最優決策序列中是否包含乙個最優決策自序列。使用動態規劃時,所求問題應具有以下兩種性質。1.最優子結構性質 所求問題的最優子結構性質是採用動態規...

演算法基本思想之動態規劃

動態規劃 談到演算法最優解 則首先會想到兩個演算法第乙個是貪心演算法,其次才會是動態規劃 再者從問題解決規模的相似程度來看,可能會引出另外的乙個演算法叫做 分治法 更準確的來講叫做分治思想 有乙個問題是 現在假定學校要從計算機工程系選出本年度的三好學生,假定其名額一共有三個,其中學校計算機工程系的學...

動態規劃演算法的基本思想 演算法 動態規劃

演算法 動態規劃 首先學習動態規劃,我們要先知道什麼是動態規劃?演算法導論這本書是這樣介紹這個演算法的,動態規劃與分治方法類似,都是通過組合子問題的解來來求解原問題的。再來了解一下什麼是分治方法,以及這兩者之間的差別,分治方法將問題劃分為互不相交的子問題,遞迴的求解子問題,再將它們的解組合起來,求出...