[算法入门笔记] 18. 动态规划
動態(tài)規(guī)劃往往是有套路的,但套路是建立在熟練的基礎(chǔ)上的~
文章目錄
- 0 建議
- 1 機(jī)器人達(dá)到指定位置的方法數(shù)
- 1.1 暴力遞歸
- 1.2 記憶化搜索
- 1.3 動態(tài)規(guī)劃
- 2 換錢的最少貨幣數(shù)
- 2.1 暴力遞歸
- 2.2 記憶化搜索
- 2.3 動態(tài)規(guī)劃
- 3 紙牌博弈問題
- 3.1 暴力遞歸
- 3.2 動態(tài)規(guī)劃
- 4 高維動態(tài)規(guī)劃
- 4.1 中國象棋馬的跳法
- 2.1.1 暴力遞歸
- 2.1.2 動態(tài)規(guī)劃
- 2.2 生存問題
- 2.2.1 暴力遞歸
- 4.2.2 動態(tài)規(guī)劃
- 5 空間壓縮技巧
- 壓縮技巧1
- 壓縮技巧2
- 壓縮技巧3
- 壓縮技巧4
- 壓縮技巧5
- 矩陣的最小路徑和
- 暴力遞歸
- 動態(tài)規(guī)劃
- 動態(tài)規(guī)劃+空間壓縮技巧
0 建議
動態(tài)規(guī)劃流程
緩存結(jié)構(gòu)如何優(yōu)化成表結(jié)構(gòu)
1 機(jī)器人達(dá)到指定位置的方法數(shù)
[問題]
假設(shè)有排成一行的N個位置,記為 [ 1 , N ] ( N ≥ 2 ) [1,N](N\ge2) [1,N](N≥2)。開始時機(jī)器人在其中的M位置上(M一定是 [ 1 , N ] [1,N] [1,N]中的一個),機(jī)器人可以往左走或者往右走。
如果機(jī)器人來到1位置,那么下一步只能往右來到2位置;
如果機(jī)器人來到N位置,那么下一步只能往左來到N-1位置。
規(guī)定機(jī)器人必須走K步,最終能來到Р位置(P也一定是 [ 1 , N ] [1,N] [1,N]中的一個)的方法有多少種。給定四個參數(shù)N、M、K、P,返回方法數(shù)。
[示例]
上面的參數(shù)代表所有位置為1 2 3 4 5。機(jī)器人最開始在2位置上,必須經(jīng)過3步,最后到達(dá)3位置。走的方法只有如下3種:
- 從2到1,從1到2,從2到3
- 從2到3,從3到2,從2到3
- 從2到3,從3到4,從4到3
所以返回方法數(shù)3。
N=3。M=1,K=3,P=3上面的參數(shù)代表所有位置為1 2 3。機(jī)器人最開始在1位置上,必須經(jīng)過3步,最后到達(dá)3位置。怎么走也不可能,所以返回方法數(shù)0。
題目特點分析
本題是經(jīng)典的從左向右嘗試模型,考慮base case,然后有兩種狀態(tài),向左走和向右走
1.1 暴力遞歸
遞歸函數(shù)參數(shù)有cur,表示當(dāng)前處在的位置和rest,表示剩余步數(shù),返回多少走法
如果走完所有步數(shù),最終位置停在P位置,說明答案有效,返回一種答案;如果最終位置不在P位置,說明答案無效,返回0種答案
dfs表示如果當(dāng)前來到cur位置,還剩下rest步要走,下一步的走法
- 如果 c u r = = 1 cur==1 cur==1,下一步只能走2位置,后續(xù)剩下rest-1步數(shù)
- 如果 c u r = = N cur==N cur==N,下一步只能走N-1位置,后續(xù)剩下rest-1步數(shù)
- 如果 c u r ∈ ( 1 , N ) cur\in(1,N) cur∈(1,N),下一步可以走cur-1位置或者cur+1位置,后續(xù)剩下rest-1步數(shù)
1.2 記憶化搜索
由于在暴力遞歸過程存在大量重復(fù)計算,復(fù)雜度是指數(shù)級別,因此設(shè)計緩存結(jié)構(gòu)存儲計算過的結(jié)果。
public int dfs(int N, int P, int cur, int rest, int[][] cache) {// 查看緩存中是否具有答案if (cache[rest][cur] != -1) {return cache[rest][cur];}// 遞歸終止條件修改成緩存結(jié)構(gòu)if (rest == 0) {// 如果剩余步數(shù)為0,并且來到P位置,答案有效cache[rest][cur] = cur == P ? 1 : 0;return cache[rest][cur];}// 單次遍歷邏輯if (cur == 1) { // 來到1位置只能向右走cache[rest][cur] = dfs(N, P, cur + 1, rest - 1);return cache[rest][cur];}if (cur == N) { // 來到N位置只能向左走cache[rest][cur] = dfs(N, P, cur - 1, rest - 1);return cache[rest][cur];}// 來到一般位置可以向左或者向右走cache[rest][cur] = dfs(N, P, cur - 1, rest -1) + dfs(N, P, cur + 1, rest -1);return cache[rest][cur]; }/*** 主函數(shù)調(diào)用dfs* @param N N個位置* @param M 當(dāng)前在M位置* @param K 只能走K步* @param P 目標(biāo)位置P* @return 返回從M位置出發(fā),只走K步,最終到達(dá)P位置的方法數(shù)*/ public int ways(int N, int M, int K, int P) {// 非法條件if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {return 0;}// 確定可變參數(shù)范圍 rest范圍[0,K],cur范圍[1,N]int[][] cache= new int[K + 1][N + 1];// 初始化緩存結(jié)構(gòu)for (int i = 0; i <= K; ++i) {for (int j = 0; j <= N; j++) {cache[i][j] = -1;}}// 調(diào)用dfs函數(shù)return dfs(N, P, M, K, cache); }1.3 動態(tài)規(guī)劃
使用該方法優(yōu)化成dp前提是所求問題具有無后效性,即一個遞歸狀態(tài)的返回值與怎么到達(dá)這個狀態(tài)的路徑無關(guān)
分析是否具有后效性
- dfs兩個固定參數(shù)N、P,任何時候都不變,說明N和P與具體遞歸狀態(tài)無關(guān),關(guān)注其他兩個可變參數(shù)rest和cur
- dfs(5,5)出現(xiàn)了兩次,不管從dfs(4,6)來到dfs(5,5),還是從dfs(6,6)來到dfs(5,5),只要是當(dāng)前來到5位置,還剩5步,返回值都是不變的,所以是一個無后效性問題
優(yōu)化步驟
- 可變參數(shù)為cur和rest
- rest作為行,cur作為列,映射成二維表,返回值為dp[rest][cur]
- 對于N=7,P=5,M=4,K=9,最終答案在dp[9][4]
- 填寫base case位置
分析普遍位置如何依賴其他位置
if (cur == 1) {dfs(N, P, 2, rest - 1); } if (cur == N) {dfs(N, P, N - 1, rest - 1); } return dfs(N, P, cur - 1, rest - 1) + dfs(N, P, cur + 1, rest - 1);- 如果cur在1位置,最終返回值 d p [ r e s t ] [ c u r ] = d p [ r e s t ? 1 ] [ 2 ] dp[rest][cur]=dp[rest-1][2] dp[rest][cur]=dp[rest?1][2]A點依賴B
- 如果cur在N位置,最終返回值 d p [ r e s t ] [ c u r ] = d p [ r e s t ? 1 ] [ N ? 1 ] dp[rest][cur]=dp[rest-1][N-1] dp[rest][cur]=dp[rest?1][N?1] C點依賴D
- 如果cur在中間位置,最終返回值 d p [ r e s t ] [ c u r ] = d p [ r e s t ? 1 ] [ c u r ? 1 ] + d p [ r e s t ? 1 ] [ c u r + 1 ] dp[rest][cur]=dp[rest-1][cur-1]+dp[rest-1][cur+1] dp[rest][cur]=dp[rest?1][cur?1]+dp[rest?1][cur+1] E點依賴F、G
確定計算順序,求出最終答案
說明每一行的值依賴上一行的值
本題動態(tài)規(guī)劃解法就是把 N × K N×K N×K規(guī)模的表填好,填寫每個位置的復(fù)雜度是 O ( 1 ) O(1) O(1),整個時間復(fù)雜度是 O ( N × K ) O(N×K) O(N×K)
/*** 動態(tài)規(guī)劃版本* @param N N個位置* @param M 當(dāng)前位置* @param K 只能走K步* @param P 目標(biāo)位置* @return*/ public int ways(int N, int P, int M, int K) {// 非法條件if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {return 0;}// 定義dp數(shù)組int[][] dp = new int[K + 1][N + 1];// 填寫簡單位置的答案dp[0][P] = 1;// 填寫普遍位置for (int rest = 1; rest <= K; rest++) {for (int cur = 1; cur <= N; cur++) {// 如果來到位置1,下一步只能走2位置if (cur == 1) {dp[rest][cur] = dp[rest - 1][2];} else if (cur == N) { // 如果來到位置N,下一步只能走N-1位置dp[rest][cur] = dp[rest - 1][N - 1];} else {dp[rest][cur] = dp[rest - 1][cur - 1] + dp[rest - 1][cur + 1];}}} // 返回當(dāng)剩余步數(shù)K,初始位置M的答案return dp[K][M]; }2 換錢的最少貨幣數(shù)
[問題]
給定數(shù)組arr,arr 中所有的值都為正數(shù)且不重復(fù)。每個值代表一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定一個整數(shù)aim,代表要找的錢數(shù),求組成aim的最少貨幣數(shù)。
[示例]
4張5元可以組成20元,其他的找錢方案都要使用更多張的貨幣,所以返回4。
arr=[5,2,3],aim=0不用任何貨幣就可以組成0元,返回0。
arr=[3,5],aim=2根本無法組成2元,錢不能找開的情況下默認(rèn)返回-1。
2.1 暴力遞歸
遞歸函數(shù)參數(shù)有i,表示當(dāng)前處在的位置和rest,表示剩余面值,返回組合數(shù)
如果走完所有步數(shù)i==arr.length,如果剩余面值為0,表示不需要貨幣了,返回0;如果剩余面值不為0,表示無法組成目標(biāo)面值,返回-1
dfs表示如果當(dāng)前來到i位置,還剩下rest面值,下一步的組合方式
- cur位置前表示已經(jīng)做出的選擇,i位置之后表示后續(xù)將要做出的選擇
- 從cur位置出發(fā)選擇任意 [ 0 , K ] [0,K] [0,K]當(dāng)前貨幣組成面值
- cur位置狀態(tài)具有選和不選兩種狀態(tài)
2.2 記憶化搜索
致命錯誤:數(shù)組無效狀態(tài)沒有初始化,這里-2表示未填寫狀態(tài),-1表示無效狀態(tài),非basecase后面有個判斷是否無效,該狀態(tài)沒有初始化,還有注意初始化順序
public int minCoins(int[] arr, int aim) {if (arr == null || arr.length == 0 || aim < 0) {return -1;}int[][] cache = new int[arr.length + 1][aim + 1];for (int i = 0; i <= arr.length; i++) {for (int j = 0; j <= aim; j++) {cache[i][j] = -2;}}return dfs(arr, 0, aim, dp); }public int dfs(int[] arr, int i, int rest, int[][] cache) {for (int row = 0; row < dp.length; row++) {for (int col = 0; col < cache[0].length; col++) {cache[row][col] = -1;}}if (cur == arr.length) {cache[i][rest] = rest == 0 ? 0 : -1;return cache[i][rest];}// 嘗試當(dāng)前貨幣0...k張情況,但不能超過restfor (int k = 0; k <= rest; k++) {// 使用k張arr[i],剩下的面值是rest-k*arr[i]// next表示決策i位置向后剩下的面值(arr[i+1...N-1])int next = dfs(arr, i+ 1, rest - k * arr[i]);if (next != -1) { // 當(dāng)i后續(xù)決策合法時// 在使用k張當(dāng)前貨幣并且后續(xù)位置合法的情況下返回較少的貨幣數(shù)// next+k表示整個決策過程使用的貨幣cache[i][rest] = cache[i][rest] == -1 ? next + k : Math.min(cache[i][rest], next + k);}}return cache[i][rest]; }2.3 動態(tài)規(guī)劃
優(yōu)化步驟
- 可變參數(shù)為i和rest
- rest作為行,i作為列,映射成二維表,剩余面值數(shù)不超過aim,因此 r e s t ∈ [ 0 , a i m ] rest\in[0,aim] rest∈[0,aim]
- 確定最終答案dfs(arr,0,aim), d p [ 0 ] [ a i m ] dp[0][aim] dp[0][aim]
5. 填寫普遍位置依賴
dfs(arr,i,rest)返回值就是 d p [ i ] [ r e s t ] dp[i][rest] dp[i][rest]
表中右上角位置時d p [ i ] [ r e s t ] p[i] [rest] p[i][rest],根據(jù)dfs(arr,i,rest),
d p [ i ] [ r e s t ] = min ? { d p [ i + 1 ] [ r e s t ? 0 ? a r r [ i ] ] + 0 dp[i] [rest] = \min\{dp[i+1] [rest - 0*arr[i]] + 0 dp[i][rest]=min{dp[i+1][rest?0?arr[i]]+0,
? d p [ i + 1 ] [ r e s t ? 1 ? a r r [ i ] ] + 1 \quad \quad dp[i+1] [rest - 1*arr[i]] + 1 dp[i+1][rest?1?arr[i]]+1,
? . . . \quad\quad\quad\quad\quad\quad\quad... ...
? d p [ i + 1 ] [ r e s t ? k ? a r r [ i ] ] + k } dp[i+1] [rest - k*arr[i]] + k\} dp[i+1][rest?k?arr[i]]+k}
要想得到 a r r [ i ] [ r e s t ] arr[i] [rest] arr[i][rest],必須得到i+1行的值
求 d p [ i ] [ r e s t ] dp[i] [rest] dp[i][rest] 前, d p [ i ] [ r e s t ? a r r [ i ] ] dp[i] [rest-arr[i]] dp[i][rest?arr[i]]已經(jīng)計算過
d p [ i ] [ r e s t ? a r r [ i ] ] = min ? { dp[i] [rest-arr[i]] = \min\{ dp[i][rest?arr[i]]=min{
? d p [ i + 1 ] [ r e s t ? 1 ? a r r [ i ] ] + 0 \quad \quad\quad dp[i+1] [rest - 1 * arr[i]] + 0 dp[i+1][rest?1?arr[i]]+0,
? d p [ i + 1 ] [ r e s t ? 2 ? a r r [ i ] ] + 1 , \quad \quad\quad dp[i+1] [rest - 2*arr[i]] + 1, dp[i+1][rest?2?arr[i]]+1,
? . . . \quad \quad\quad\quad \quad\quad\quad \quad\quad... ...
? d p [ i + 1 ] [ r e s t ? k ? a r r [ i ] ] + k ? 1 } dp[i+1] [rest - k*arr[i]] + k-1\} dp[i+1][rest?k?arr[i]]+k?1}
圖解
因此, d p [ i ] [ r e s t ] = m i n ( d p [ i ] [ r e s t ? a r r [ i ] + 1 , d p [ i + 1 ] [ r e s t ] ) dp[i] [rest] = min (dp[i] [rest-arr[i] + 1, dp[i+1] [rest]) dp[i][rest]=min(dp[i][rest?arr[i]+1,dp[i+1][rest]),就是說dp[i] [rest]依賴下面一個位置和左邊一個位置
最后一排值已經(jīng)確定,剩下的位置只依賴下面和左邊的位置,只要求從左到右求倒數(shù)第二排,從左到右求倒數(shù)第三排…從做到到右求第一排即可
3 紙牌博弈問題
[問題]
給定一個整型數(shù)組arr,代表數(shù)值不同的紙牌排成一條線。玩家A和玩家B依次拿走每張紙牌,規(guī)定玩家A先拿,玩家B后拿,但是每個玩家每次只能拿走最左或最右的紙牌,玩家A和玩家B都絕頂聰明。請返回最后獲勝者的分?jǐn)?shù)。
[示例]
開始時,玩家A只能拿走1或4。如果開始時玩家A拿走1,則排列變?yōu)閇2,100,4],接下來玩家B可以拿走2或4,然后繼續(xù)輪到玩家A…
如果開始時玩家A拿走4,則排列變?yōu)閇1,2,100],接下來玩家B可以拿走1或100,然后繼續(xù)輪到玩家A…
玩家A作為絕頂聰明的人不會先拿4,因為拿4之后,玩家B將拿走100。所以玩家A會先拿1,讓排列變?yōu)閇2,100,4],接下來玩家B不管怎么選,100都會被玩家A拿走。玩家A會獲勝,分?jǐn)?shù)為101。所以返回101。
開始時,玩家A不管拿1還是2,玩家B作為絕頂聰明的人,都會把100拿走。玩家B會獲勝,分?jǐn)?shù)為100。所以返回100。
3.1 暴力遞歸
- 定義遞歸函數(shù)first(i,j),表示arr[i…j]這個排列上的紙牌被絕頂聰明的人先拿,最終返回的分?jǐn)?shù)
- 定義遞歸函數(shù)second(i,j),表示arr[i…j]這個排列上的紙牌被絕頂聰明的人后拿,最終返回的分?jǐn)?shù)
分析先手first(i,j)
- i==j,即只剩一張牌。當(dāng)然會被先拿紙牌的人拿走,返回arr[i]
- i!=j,當(dāng)前拿紙牌的人,要么拿arr[i],要么拿arr[j]。
- 如果拿arr[i],那么排列只剩下arr[i+1…j]。對當(dāng)前玩家,面對arr[i+1…j],他將成為后手,后續(xù)獲得分?jǐn)?shù)是second(i+1,j)
- 如果拿arr[j],那么排列只剩下arr[i…j-1]。對當(dāng)前玩家,面對arr[i…j-1],他將成為后手,后續(xù)獲得分?jǐn)?shù)是second(i,j-1)
- 作為絕頂聰明的人,兩種決策都是最優(yōu)的,返回max{arr[i]+second(i+1,j), arr[j]+second(i,j-1)}
分析后手second(i,j)
- i==j,即只剩下一張牌。作為后手,什么都拿不到,得分0
- i!=j,該玩家對手先拿紙牌。對手要么拿走arr[i],要么拿走arr[j]
- 如果對手拿走arr[i],排列剩下arr[i+1…j],然后輪到該玩家先拿
- 如果對手拿走arr[j],排列剩下arr[i…j-1],然后輪到該玩家先拿
- 對手也是絕頂聰明的人,返回Min{first(i+1,j), first(i,j-1)}
先手函數(shù)
public int first(int[] arr, int i, int j) {if (i == j) {return arr[i];}return Math.max(arr[i] + second(arr, i + 1, j),arr[j] + second(arr, i, j - 1)); }后手函數(shù)
public int second(int[] arr, int i, int j) {if (i == j) {return 0;}return Math.min(first(arr, i + 1, j),first(arr, i, j - 1)); }主函數(shù)調(diào)用
public int win(int[] arr) {if (arr == null || arr.length == 0) {return 0;}return Math.max(first(arr, 0, arr.length - 1), second(arr, 0, arr.length - 1)); }3.2 動態(tài)規(guī)劃
經(jīng)典的范圍嘗試模型
1.分析first(i,j)可變i,j參數(shù)范圍
2.標(biāo)出計算的終止位置
3.標(biāo)出basecase
-
i不會超過j,即下三角部分無效
-
first:對角線basecase
if (i == j) {return arr[i]; } -
second 對角線basecase
if (i == j) {return 0; }
3.標(biāo)出非basecase的普遍位置依賴
-
first
return Math.max(arr[i] + second(arr, i + 1, j),arr[j] + second(arr, i, j - 1) ); -
second
return Math.min(first(arr, i + 1, j),first(arr, i, j - 1) );
5.確定計算次序
public int win(int[] arr) {if (arr == null || arr.length == 0) {return 0;}int[][] first = new int[arr.length][arr.length];int[][] second = new int[arr.length][arr.length];for (int col = 0; col < arr.length; col++) {first[col][col] = arr[col];for (int row = col - 1; row >= 0; row--) {first[row][col] = Math.max(arr[row] + second[row + 1][col], arr[col] + second[row][col - 1]);second[row][col] = Math.min(first[row + 1][col], first[row][col - 1]);}}return Math.max(first[0][arr.length - 1], second[0][arr.length - 1]); }4 高維動態(tài)規(guī)劃
4.1 中國象棋馬的跳法
[問題]
把棋盤放入第一象限,棋盤的最左下角是 ( 0 , 0 ) (0,0) (0,0)位置。那么整個棋盤就是橫坐標(biāo)上9條線、縱坐標(biāo)上10條線的一個區(qū)域。給你三個參數(shù),x,y,k,返回如果“馬”從(0,0)位置出發(fā),必須走k步,最后落在(x, y)上的方法數(shù)有多少種?
2.1.1 暴力遞歸
public int getWays(int x, int y, int step) {return process(x, y, step); }public int process(int x, int y, int step) {if (x < 0 || x > 8 || y < 0 || y > 9) {return 0;}if (step == 0) {return (x == 0 && y == 0) ? 1 : 0;}return process(x - 1, y - 2, step - 1) +process(x + 1, y - 2, step - 1) +process(x - 2, y - 1, step - 1) +process(x + 2, y - 1, step - 1) +process(x - 2, y + 1, step - 1) +process(x + 2, y + 1, step - 1) +process(x - 1, y + 2, step - 1) +process(x + 1, y + 2, step - 1); }2.1.2 動態(tài)規(guī)劃
1.分析可變x,y,step參數(shù)范圍
2.標(biāo)出計算終止位置
3.標(biāo)出basecase
if (step == 0) {return (x == 0 && y == 0) ? 1 : 0; } public int getWays(int x, int y, int step) {if (x < 0 || x > 8 || y < 0 || y > 9 || step < 0) {return 0;}int[][][] dp = new int[9][10][step + 1];// base casedp[0][0][0] = 1;// 從底層往上計算for (int height = 1; height <= step; height++) {for (int row = 0; row < 9; row++) {for (int col = 0; col < 10; col++) {dp[row][col][height] += getValue(dp, row - 1, col - 2, height - 1);dp[row][col][height] += getValue(dp, row + 1, col - 2, height - 1);dp[row][col][height] += getValue(dp, row - 2, col - 1, height - 1);dp[row][col][height] += getValue(dp, row + 2, col - 1, height - 1);dp[row][col][height] += getValue(dp, row - 2, col + 1, height - 1);dp[row][col][height] += getValue(dp, row + 2, col + 1, height - 1);dp[row][col][height] += getValue(dp, row - 1, col + 2, height - 1);dp[row][col][height] += getValue(dp, row + 1, col + 2, height - 1);}}}return dp[x][y][step]; }// 防止出現(xiàn)越界,同時返回數(shù)組值 public int getValue(int[][][] dp, int row, int col, int step) {if (row < 0 || row > 8 || col < 0 || col > 9) {return 0;}return dp[row][col][step]; }2.2 生存問題
[問題]
給定五個參數(shù)n, m,i, j, k。表示在一個 N × M N×M N×M的區(qū)域,Bob處在 ( i , j ) (i,j) (i,j)點,每次Bob等概率的向上、下、左、右四個方向移動一步,Bob必須走K步。如果走完之后,Bob還停留在這個區(qū)域上,就算Bob存活,否則就算Bob死亡。請求解Bob的生存概率,返回字符串表示分?jǐn)?shù)的方式。
2.2.1 暴力遞歸
public String bob(int N, int M, int i, int j, int k) {// 總步數(shù)4^klong allStep = (long)Math.pow(4,k);long live = process(N, M, i, j, k);long gcd = gcd(allStep, live);return String.valueOf((live / gcd) + "/" + (allStep / gcd)); }/*** N*M區(qū)域內(nèi),Bob從(row,col)位置出發(fā),走rest步,獲得生存點數(shù)* @param N 矩陣長度* @param M 矩陣寬度* @param row 出發(fā)位置的橫坐標(biāo)* @param col 出發(fā)位置的縱坐標(biāo)* @param rest 剩余步數(shù)* @return 生存點數(shù)*/ public long process(int N, int M, int row, int col, int rest) {//違規(guī)條件if(row < 0 || row == N || col < 0 || col == M) {return 0;}if (rest == 0) { //剩余步數(shù)0,說明走完return 1;}long live =//往上走process(N, M, row - 1, col, rest - 1) +//往下走process(N, M, row + 1, col, rest - 1) +//往左走process(N, M, row, col - 1, rest - 1) +//往右走process(N, M, row, col + 1, rest - 1);return live; }// 最大公約數(shù) public long gcd(long m, long n) {return n == 0 ? m : gcd(n, m % n); }4.2.2 動態(tài)規(guī)劃
public String bob(int N, int M, int i, int j, int K) {int[][][] dp = new int[N + 2][M + 2][K + 1];// base casefor (int row = 1; row <= N; row++) {for (int col = 1; col <= M; col++) {dp[row][col][0] = 1;}}// 從底層往高層計算for (int rest = 1; rest <= K; rest++) {for (int row = 1; row <= N; row++) {for (int col = 1; col <= M; col++) {dp[row][col][rest] = dp[row - 1][col][rest - 1];dp[row][col][rest] += dp[row + 1][col][rest - 1];dp[row][col][rest] += dp[row][col - 1][rest - 1];dp[row][col][rest] += dp[row][col + 1][rest - 1];}}}long all = (long) Math.pow(4, K);long live = dp[i + 1][j + 1][K];long gcd = gcd(all, live);return String.valueOf((live / gcd) + "/" + (all / gcd)); }5 空間壓縮技巧
壓縮技巧1
將二維數(shù)組壓縮成一維數(shù)組
壓縮技巧2
壓縮技巧3
壓縮技巧4
壓縮技巧5
其他技巧
矩陣的最小路徑和
暴力遞歸
1.考慮走到邊界,怎么處理 2.寫對返回值
1. 分析basecase,終止位置—2.分析邊界條件和違規(guī)條件,返回值怎么處理—3.普遍位置時如何處理返回值
- 要得到a(i,j)到b路徑和,先求a右邊點到b的路徑和right,以及a下面點到點b的路徑和down,最后a到b路徑和為 min ? { r i g h t , d o w n } + a r r [ i ] [ j ] \min\{right,down\}+arr[i] [j] min{right,down}+arr[i][j]
- a(i,j)到達(dá)
- 只能向右移動,其路徑和是a點+右邊到b的路徑和
- a(i,j)到達(dá)最后一列,他只能向下移動,其路徑和是a點+下邊到b的路徑和
動態(tài)規(guī)劃
-
對于第一行所有的位置(0,j)來說,從(0,0)位置到(0,j)位置只能向右走,所以(0,0)到(0,j)位置的路徑和是m[0] [0…j]累加的結(jié)果
-
對于m的第一列的所有位置來說,即(i,0)從(0,0)位置走到(i,0)位置只能向下走,所以(0,0)位置到(i,0)位置的路徑和就是m[0…i] [0]累加的結(jié)果
-
除了第一行和第一列的位置外,都有左邊位置(i-1,j)和上邊位置(i,j-1)
-
從(0,0)到(i,j)位置的路徑必然經(jīng)過位置(i-1,j)或位置(i,j-1)
-
所以 d p [ i ] [ j ] = min ? { d p [ i ? 1 ] [ j ] , d p [ i ] [ j ? 1 ] } + m [ i ] [ j ] dp[i] [j] = \min\{dp[i-1] [j],dp[i] [j-1]\}+m[i] [j] dp[i][j]=min{dp[i?1][j],dp[i][j?1]}+m[i][j]
-
含義是比較從(0,0)位置開始,經(jīng)過(i-1,j)位置最終到到達(dá)(i,j)的最小路徑和經(jīng)過(i,j-1)位置最終到達(dá)(i,j)的最小路徑,誰最小
-
除第一行和第一列位置外,每一個位置考慮從左邊到達(dá)自己的路徑和更小還是從上邊到達(dá)自己的路徑和更小,最右下角位置就是整個問題的答案
動態(tài)規(guī)劃+空間壓縮技巧
1 3 5 9 8 1 3 4 5 0 6 1 8 8 4 0 \begin{matrix} 1 & 3 & 5 & 9\\ 8 & 1 & 3 & 4\\ 5 & 0 & 6 & 1\\ 8 & 8 & 4 & 0\\ \end{matrix} 1858?3108?5364?9410?
- 生成大小為 min ? { M , N } \min\{M,N\} min{M,N}的一維數(shù)組,本測試用例長度為4,初始 a r r = [ 0 , 0 , 0 , 0 ] arr=[0,0,0,0] arr=[0,0,0,0],從 ( 0 , 0 ) (0,0) (0,0)位置出發(fā)到達(dá)m第一行的每個位置,最小路徑和時從 ( 0 , 0 ) (0,0) (0,0)位置開始依次累加的結(jié)果, a r r = [ 1 , 4 , 9 , 18 ] arr=[1,4,9,18] arr=[1,4,9,18],此時arr[j]代表從 ( 0 , 0 ) (0,0) (0,0)位置到 ( 0 , j ) (0,j) (0,j)位置的最小路徑和
-
準(zhǔn)備把a(bǔ)rr[j]的值更新成 ( i , j ) (i,j) (i,j)位置上的和
-
更新arr[0], a r r [ 0 ] = a r r [ 0 ] + m [ 1 ] [ 0 ] arr[0]=arr[0]+m[1] [0] arr[0]=arr[0]+m[1][0]
- 更新arr[1]
- 從 [ 1 ] [ 0 ] [1] [0] [1][0]位置到達(dá) [ 1 ] [ 1 ] [1] [1] [1][1]位置 d p [ 1 ] [ 0 ] + m [ 1 ] [ 1 ] dp[1] [0]+m[1] [1] dp[1][0]+m[1][1]
- 從 [ 0 ] [ 1 ] [0] [1] [0][1]位置到達(dá) [ 1 ] [ 1 ] [1] [1] [1][1]位置 d p [ 0 ] [ 1 ] + m [ 1 ] [ 1 ] dp[0] [1]+m[1] [1] dp[0][1]+m[1][1]
最終arr更新成 [ 9 , 5 , 8 , 2 ] [9, 5, 8, 2] [9,5,8,2]
- 整個過程不斷滾動更新arr[],讓arr[]依次變成個dp矩陣的每一行,最終變成dp矩陣最后一行的值
NOTICE
- 給定矩陣列數(shù)小于行數(shù)(N<M),可以進(jìn)行空間壓縮
- 給定矩陣列數(shù)大于行數(shù)(M<N),就生成長度為M的arr,令arr更新成dp的每一列的值,從左向右滾動
總結(jié)
以上是生活随笔為你收集整理的[算法入门笔记] 18. 动态规划的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2018(农历年)封山之作,和我一起嚼烂
- 下一篇: 实验一:彩色空间转换(YUV2RGB)