動態規劃之一陽指——轉移狀態的設定橫向對比
粗略介紹解決動態規劃的整個全域性過程,這裡提供兩個思路:
第一種,固定格式:
1確定狀態
需要兩個意識:
最後一步:最優策略的最後一步。
子問題:子問題與原問題類似
2轉移方程
3初始條件和邊界情況
題目的限制條件
4計算順序
第二種,遞迴改寫,但是對於有些問題遞迴是不太好寫的。
1 設計暴力演算法——遞迴(遞迴的思路相當清晰,寫出來的程式碼相當短小精悍)
2 找到冗餘設計並存儲狀態(-維,二維,三維陣列,甚至用Map )
3 更改遞迴式(狀態轉移方程),儲存遞迴返回的值。
4 自底向上計算最優解(程式設計方式)
從區域性上看,本文主要想解決的問題是如何分析動態規劃的狀態問題,狀態是解決轉移方程的關鍵鑰匙,不論採用何種方法解決問題,狀態是一定要事先確定的!
設定狀態——
一般是採用陣列儲存的方式:
1 一維陣列
每個元素 f[ i ]的含義
2 二維陣列
f[ i ][ j ],i * j個元素,每個元素的含義
DP型別
最大最小值規劃:
LintCode 669: Coin Change
你有三種硬幣,分別面值2元,5元和7元, 每種硬幣都有足夠多,買一本書需要27元
如何用最少的硬幣組合正好付清,不需要對方找錢。
輸入: [1, 2, 5] 11 輸出: 3 解釋: 11 = 5 + 5 + 1
輸入: [2] 3 輸出: -1
分析狀態:由於是線性順序,可以採用一維陣列 f[ ],f[ ]儲存硬幣個數,故設
狀態 f[ X ] = 最少用多少枚硬幣拼出 X,X為數值;
硬幣有三種:2、5、7,
拼出X所需要的最少硬幣數:
f[ X ] = min{ f[ X - 2 ] + 1, f[ X - 5 ] + 1, f[ X - 7 ] + 1 }
LintCode 114 Unique Paths有一個機器人的位於一個 m × n 個網格左上角。
機器人每一時刻只能向下或者向右移動一步。機器人試圖達到網格的右下角。
問有多少條不同的路徑?
eg1: Input: n = 1, m = 3 Output: 1 Explanation: Only one path to target position. eg2: Input: n = 3, m = 3 Output: 6 Explanation: D : Down R : Right 1) DDRR 2) DRDR 3) DRRD 4) RRDD 5) RDRD 6) RDDR
分析:網格結構,座標的移動需座標 ( x, y)的變化,故開闢一個二維陣列 valueDp[ ][ ]。但是這個陣列如何設定含義呢?
最後一步:無論機器人用何種方式到達右下角,總有最後挪動的一步;
每一步向右或者向下,右下角座標設為 ( m - 1, n - 1 ),那麼前一步機器人一定是在 (m - 2, n - 1 )或者 (m - 1, n - 2)
狀態:設 f[ i ][ j ]為機器人有多少種方式從左上角走到 (i, j);
自然地,f[ i - 1 ][ j ]表示機器人有多少種方式走到 (i - 1, j);
自然地,f[ i ][ j - 1]表示機器人有多少種方式走到 (i, j - 1)
那麼,對於任意一個格子( i, j ),有兩個方向可以到達此位置,根據加法組合原理,可以推匯出轉移方程:
f[ i ][ j ] = f[ i - 1 ][ j ] + f[ i ][ j - 1]
存在型DP
LintCode116. 跳躍遊戲jump-game
給出一個非負整數陣列,你最初定位在陣列的第一個位置;
陣列中的每個元素代表你在那個位置可以跳躍的最大長度。
判斷你是否能到達陣列的最後一個位置。
e.g.1: 輸入 : [2,3,1,1,4] 輸出 : true e.g.2: 輸入 : [3,2,1,0,4] 輸出 : false
分析:題目是一維陣列,判斷結果是否可達,所以採用一維陣列狀態
狀態:設 f[ j ] 表示青蛙能不能跳到石頭 j;
那麼選擇上一個石頭 i的條件是什麼?為什麼上一個石頭不是 j - 1呢?這是設定變數i的原因。
丟擲來三個問題:
怎麼選擇石頭 i;能不能跳到石頭i;最後一步的距離不能超過 ai;
OR0<=i<j ; f[ i ] i + a[ i ] >= j
就是構成轉移方程的元素:
f[ j ] = OR 0<=i<j (f[ i ] AND i + a[ i ] >= j )
LintCode 191. 乘積最大子序列maximum-product-subarray
給定a[0], ... a[n-1],找出一個序列中乘積最大的連續子序列(至少包含一個數)。
樣例 1: 輸入:[2,3,-2,4] 輸出:6 樣例 2: 輸入:[-1,2,4,1] 輸出:8
分析狀態:其乘積最大,為一維陣列,記為 f[ ],
乘積的結果和數字的正負號相關,當前值為正數最大值,再乘一個負值,就變成負數最小值了,再乘負數,變成正數最大值;
考慮到一個數組儲存空間和序列相關的話,不夠用,所以選取兩個一維陣列,f[ ]和 g[ ]
f[ j ] = 以 a[ j ]結尾的連續子序列的最大乘積
g[ j ] =以 a[ j ]結尾的連續子序列的最小乘積
故狀態:
f[ j ] = 以 a[ j ] 結尾的連續子序列的最大乘積
情況1 :子序列就是a[ j ]本身,a[ j ];
情況2 :以a[ j - 1 ]結 尾的連續子序列的最大/最小乘積,乘上 a[ j ],max{ a[ j ] * f[ j - 1 ], a[ j ] * g[ i - 1 ] }
轉移方程:f[ j ] = max{ a[ j ],max{ a[ j ] * f[ j - 1 ], a[ j ] * g[ i - 1 ] } | j > 0 }
座標型動態規劃:陣列下標 [ i ][ j ] 即座標 (i, j)
LintCode 115: Unique Paths II
給定 m 行 n 列的網格,有一個機器人從左上角(0,0)出發,每一步可以向下或者向右走一步
網格中有些地方有障礙,機器人不能通過障礙格
問有多少種不同的方式走到右下角
Example 1: Input: [[0]] Output: 1 Example 2: Input: [[0,0,0],[0,1,0],[0,0,0]] Output: 2 Explanation: Only 2 different path.
狀態分析:假設座標為 (x, y),記為 (i, j)
最後一步一定是從左邊 (i, j - 1) 或上邊 (i - 1, j) 過來
設狀態 f[ i ][ j ] 表示從左上角有多少種方式走到格子 (i, j);
那麼(i, j - 1) 記為 f[ i ][ j - 1 ]表示從左邊有多少種方式走到格子(i, j - 1);
(i - 1, j) 記為 f[ i - 1 ][ j ]表示從上邊有多少種方式走到格子(i - 1, j)
所以狀態轉移方程為:
f[ i ][ j ] = f[ i - 1 ][ j ] + f[ i ][ j - 1 ]
但是如果遇到障礙怎麼辦?則
f[ i ][ j ] = 0
初始條件和邊界條件不在本文的討論範圍內。
LintCode 515 Paint House
這裡有 n 個房子在一列直線上,現在我們需要給房屋染色,分別有紅色藍色和綠色。每個房屋染不同的顏色費用也不同,你需要設計一種染色方案使得相鄰的房屋顏色不同,並且費用最小,返回最小的費用。
費用通過一個 n x 3 的矩陣給出,比如 cost[ 0 ][ 0 ] 表示房屋 0 染紅色的費用,cost[ 1 ][ 2 ] 表示房屋 1 染綠色的費用。
樣例 1: 輸入: [[14,2,11],[11,14,5],[14,3,10]] 輸出: 10 解釋: 第一個屋子染藍色,第二個染綠色,第三個染藍色,最小花費:2 + 5 + 3 = 10. 樣例 2: 輸入: [[1,2,3],[1,4,6]] 輸出: 3
顯然題目中是二維陣列,故狀態也要開闢一個二維陣列空間
當前房子i所要塗的顏色與前一個房子 i - 1塗的顏色相關,
只有三個顏色,所以二維元素個數為 3.
即可以設定狀態: valueDp[ i ][ j ]為第i個房子油漆顏色j所需要的花費;
當前房子為紅色:
valueDp[ i ][ 0 ] = min{ valueDp[ i - 1 ][ 1 ] + cost[ i - 1 ][ 0 ], valueDp[ i - 1 ][ 2 ] + cost[ i - 1 ][ 0 ] };
當前房子為藍色:
valueDp[ i ][ 1 ] = min{ valueDp[ i - 1 ][ 0 ] + cost[ i - 1 ][ 1 ], valueDp[ i - 1 ][ 2 ] + cost[ i - 1 ][ 1 ] };
當前房子為綠色:
valueDp[ i ][ 2 ] = min{ valueDp[ i - 1 ][ 0 ] + cost[ i - 1 ][ 2 ], valueDp[ i - 1 ][ 1 ] + cost[ i - 1 ][ 2 ] };
劃分型
LintCode 512 Decode Ways
有一個訊息包含A-Z
通過以下規則編碼
'A' -> 1
'B' -> 2
...
'Z' -> 26
現在給你一個加密過後的訊息,問有幾種解碼的方式
樣例 1: 輸入: "12" 輸出: 2 解釋: 它可以被解碼為 AB (1 2) 或 L (12). 樣例 2: 輸入: "10" 輸出: 1
狀態分析:字元的數值範圍在 1 ~ 26,超過這個範圍必然會被截斷。
字串的長度個數設定為 i ,對應的一維陣列狀態 f[ i ] =字元長度為 i有多少種解密方式