1. 塔樹選擇和最大問題
(見塔樹選擇和最大問題)
一個高度為N的由正整數組成的三角形,從上走到下,求經過的數字和的最大值。每次只能走到下一層相鄰的數上,例如從第3層的6向下走,只能走到第4層的2或9上。
5
8 4
3 6 9
7 2 9 5
例子中的最優方案是:5 + 8 + 6 + 9 = 28。
- 分析
直接分析,從上到下的考慮,發現無從下手好像只能遍歷,但是反方向考慮則,則發現有趣的地方,假設dp[i][j]為最下面一層到第i層j位置上的最大值,考慮上圖6這個位置,那么其dp[3][2]應該是什么呢?是下面相鄰的兩個位置的最大值+6,即dp[3][2] = max(dp[3+1][2],dp[3+1][2+1]) + a[3][2]。
據此可以推導其公式為
dp[i][j] = max(dp[i+1][j],dp[i+1][j+1]) + a[i][j]
根據上述公式編程思路如下
1、初始化最下面一排dp
2、由下往上,安裝上述公式對dp進行賦值
3、dp[1][1]為最終所求
//最下面一層直接賦值
int rs = 0;
for (int i = 0; i<FLOOR; i++)
dp[FLOOR-1][i] = a[FLOOR-1][i];
//從倒數第二行起, 按照狀態轉移方程
//dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + a[i][j]向上遞推
//直到dp[0][0], 此時dp[0][0]就是結果
for (int i = FLOOR-2; i>=0; i--)
for (int j = 0; j<=i;j++)
dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
啟示:動態規劃解決問題時,經常從后面往前考慮會瞬間明朗很多,塔數類問題還有許多其他的變形參見 動態規劃“數塔”類型題目總結
2. 乘法表問題
定義于字母表∑(a,b,c)上的乘法表如表1所示
- 表1. ∑乘法表
∑ | a |b | c
:---:|:---:|:---:|:---:
a |b | b | a
b |c | b| a
c |a | c | c
依此乘法表,對任一定義于∑上的字符串,適當加括號表達式后得到一個表達式。例如,對于字符串x=bbbba,它的一個加括號表達式為i(b(bb))(ba)。依乘法表,該表達式的值為a。試設計一個動態規劃算法,對任一定義于∑上的字符串x=x1x2…xn,計算有多少種不同的加括號方式,使由x導出的加括號表達式的值為a
要求:
輸入:輸入一個以a,b,c組成的任意一個字符串。
輸出:計算出的加括號方式數。
分析:
建立一個三位數組,用于記錄一段連續的序列內通過加括號可得到a、b、c的方式數,然后往長度方向擴展,因為每兩個字母相乘的結果已給出,所以可通過加和乘運算求出更大長度的字符串得到a、b、c的方式數具體算法:
數組維數為:p[n][n][3];
p[i][j][k] 表示 字符串xix(i+1)....xj的表達式的值為k(k>=0 k <=2,k=0表示a...) 的方式數;
遞推式為:
p[i][j][0]= sum(p[i][t][0]*p[t+1][j][2]+ p[i][t][1]*p[t+1][j][2]+p[i][t][2]*p[t+1][j][0])
p[i][j][1]與p[i][j][2]類似p[i][j][0] 的求法 t>=i 并且t <j
C++代碼:
#include <stdio.h>
#include <string.h>
int main() {
int n = 0;
char c;
int p[100][100][3] = {0};
while((c = getchar()) != '/n') {
p[n][n][c - 'a'] = 1;
n++;
}
for(int k = 1;k < n;k++) {
for(int i = 0;i < n-k;i++) {
int j = i + k;
for(int t = i;t < j;t++) {
p[i][j][0] += p[i][t][2]* p[t+1][j][0] + p[i][t][0]*p[t+1][j][2] + p[i][t][1]*p[t+1][j][2];
p[i][j][1] += p[i][t][0]*p[t+1][j][0] + p[i][t][0]*p[t+1][j][1] + p[i][t][1]*p[t+1][j][1];
p[i][j][2] += p[i][t][1]*p[t+1][j][0] + p[i][t][2]*p[t+1][j][1] + p[i][t][2]*p[t+1][j][2];
}
}
}
printf("%d/n",p[0][n-1][0]);
}
3. 爬樓梯
題目:
You are climbing a stair case. It takes n steps to reach to the top.
Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
- 分析:
動態規劃 d(i) = d(i-1) + d(i-2)
class Solution:
# @param n, an integer
# @return an integer
def climbStairs(self, n):
dp = [0, 1, 2]
if n <= 2:
return dp[n]
dp += [0 for i in range (n-2)]
for i in range (3, n + 1):
dp[i] += dp[i-1] + dp[i-2]
return dp[n]
4. 最長上升子序列(LIS)
問題描述:
設L=<a1,a2,…,an>是n個不同的實數的序列,L的遞增子序列是這樣一個子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值。
- 分析:
這里采用的是逆向思維的方法,從最后一個開始想起,即先從A[N](A數組是存放數據的數組,下同)開始,則只有長度為1的子序列,到A[N-1]時就有兩種情況,如果a[n-1] < a[n] 則存在長度為2的不下降子序列 a[n-1],a[n];如果a[n-1] > a[n] 則存在長度為1的不下降子序列 a[n-1]或者a[n]。
有了以上的思想,DP方程就呼之欲出了(這里是順序推的,不是逆序的):
DP[I]=MAX(1,DP[J]+1) J=0,1,...,I-1
但這樣的想法實現起來是)O(n^2)的。本題還有更好的解法,就是O(n*logn)。利用了長升子序列的性質來優化,以下是優化版的代碼:
//最長不降子序
const int SIZE=500001;
int data[SIZE];
int dp[SIZE];
//返回值是最長不降子序列的最大長度,復雜度O(N*logN)
int LCS(int n) { //N是DATA數組的長度,下標從1開始
int len(1),low,high,mid,i;
dp[1]=data[1];
for(i=1;i<=n;++i) {
low=1;
high=len;
while( low<=high ) { //二分
mid=(low+high)/2;
if( data[i]>dp[mid] ) {
low=mid+1;
}
else {
high=mid-1;
}
}
dp[low]=data[i];
if( low>len ) {
++len;
}
}
return len;
}
5. 背包問題
有N件物品和一個容量為V的背包。第i件物品的大小是c[i],價值是w[i]。求解將哪些物品裝入背包可使價值總和最大。
分析:
用DP[I][J] 表示前I件物品放入一個容量為J的背包可以獲得的最大價值。則
DP[I][J]= DP[I-1][J] ,J<C[I]
MAX(DP[I-1][J],DP[I-1][J-C[I]]+W[I]) , J>=C[I]
這樣實現的空間復雜度為O(VN),實際上可以優化到O(V)。以下是代碼:
const int MAXW=13000; //最大重量
const int MAXN=3450; //最大物品數量
int c[MAXN]; //物品的存放要從下標1開始
int w[MAXN]; //物品的存放要從下標1開始
int dp[MAXW];
//不需要將背包裝滿,則將DP數組全部初始化為0
//要將背包裝滿,則初始化為DP[0]=0,DP[1]…DP[V]=-1(即非法狀態)
int Packet(int n,int v) {
int i,j;
memset(dp,0,sizeof(dp));
for(i=1;i<=n;++i) {
for(j=v;j>=c[i];--j) { //這里是倒序,別弄錯了
dp[j]=MAX(dp[j],dp[j-c[i]]+w[i]);
}
}
return dp[v];
}
6. 最長公共子序列(LCS)
給出兩個字符串a, b,求它們的最長、連續的公共字串。
這很容易就想到以DP[I][J]表示A串匹配到I,B串匹配到J時的最大長度。則:
0 I==0 || J==0
DP[I][J]= DP[I-1][J-1]+ 1 A[I]==B[J]
MAX(DP[I-1][J],DP[I][J-1]) 不是以上情況
但這樣實現起來的空間復雜度為O(n^2),而上面的方程只與第I-1行有關,所以可以用兩個一維數組來代替。以下是代碼:
//最長公共子序列
const int SIZE=1001;
int dp[2][SIZE]; //兩個一維數組
//輸入兩個字符串,返回最大的長度
int LCS(const string& a,const string& b) {
int i,j,flag;
memset(dp,0,sizeof(dp));
flag=1;
for(i=1;i<=a.size();++i) {
for(j=1;j<=b.size();++j) {
if( a[i-1]==b[j-1] ) dp[flag][j]=dp[1-flag][j-1]+1;
else dp[flag][j]=MAX(dp[flag][j-1],dp[1-flag][j]);
}
flag=1-flag;
}
return dp[1-flag][b.size()];
}
另見 LCS