1. 程式人生 > 實用技巧 >動態規劃之一陽指——轉移狀態的設定橫向對比

動態規劃之一陽指——轉移狀態的設定橫向對比

粗略介紹解決動態規劃的整個全域性過程,這裡提供兩個思路:

第一種,固定格式:

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有多少種解密方式