幾篇很好的資料
動態規劃問題
- 最長非降子序列
一個序列有N個數:A[1],A[2],…,A[N],求出最長非降子序列的長度. - 股票買賣:一次交易
你得到一系列在接下來幾天的股票價格,現在你被允許只用一次交易(就是買進再賣出)來獲取最大利益 - 股票買賣:兩次交易
允許兩次買賣,但是買之前手里必須沒有股票 - 股票買賣:多次交易
允許 k 次買賣,但是買之前手里必須沒有股票 - 二維矩陣路線
平面上有N*M個格子,每個格子中放著一定數量的蘋果。你從左上角的格子開始, 每一步只能向下走或是向右走,每次走到一個格子上就把格子里的蘋果收集起來, 這樣下去,你最多能收集到多少個蘋果 - 網絡節點間最短路徑
給定網絡graph,和其內指定節點v1 v2,求出v1到v2的帶權重的最短路徑.
基本思想和策略
分析 問題 可能的 子問題,記錄子問題的狀態,通過子問題的狀態轉移函數間接推導 問題 的解.
以 動態規劃-新手到專家"最長非降子序列"問題為例,進行介紹
int v[] = {5,3,4,8,6,7}
//很明顯v[]的最長非降子序列是 3 4 6 7, 下面給出動態規劃的思考過程
- 子問題
求出v[0]~v[i] 的最長非降子序列s(i), 對與s(0)顯然s(0)= {5}
看看能不能通過s(0)推出 s(1), 然后由s(1),s(0) 推出s(2) ....
seqId | LIS | analyze |
---|---|---|
s0 | 5 | default |
s1 | 3 | v[1]<s0.max: {3} |
s2 | 3 4 | v[2]>s1.max: {3 4} v[2]<s0.max: {4} |
s3 | 3 4 8 | v[3]>s2.max: {3 4 8} v[3] > s1.max: {3 8} ... |
s4 | 3 4 6 | v[4] > s3.max: {3 4 6} ... |
s5 | 3 4 6 7 | v[5] > s4.max: {3 4 6 7} ... |
- 狀態轉移函數
s(i) = longest{ v[i], s(j), s(k),... }
其中 0<=k<j< i , 且 v[i]>= s(k).max, v[i] >= s(j).max
s(0) = { v[0] };
這樣就可以像上表一樣從s(0)利用狀態轉移函數一步步推導出s[n-1]; - 總結
分析子問題; //v[0]~v[i] 的最長非降子序列s(i)
給出已知子問題的解; // s(0)= {5}
-
嘗試推出下一個子問題;
v[1]<s0.max => s(1) = {3}
v[2]>s1.max: {3 4}
v[2]<s0.max: {4} => s(2) = {3,4}
分析子問題的狀態轉移函數;
s(i) = longest{ v[i], s(j), s(k),... }
其中 0<=k<j< i , 且 v[i]>= s(k).max, v[i] >= s(j).max整理以上思路即可;
//摘自[動態規劃-新手到專家]
#include <iostream>
using namespace std;
int lis(int A[], int n){
int *d = new int[n];
int len = 1;
for(int i=0; i<n; ++i){
d[i] = 1;
for(int j=0; j<i; ++j)
if(A[j]<=A[i] && d[j]+1>d[i])
d[i] = d[j] + 1;
if(d[i]>len) len = d[i];
}
delete[] d;
return len;
}
int main(){
int A[] = {
5, 3, 4, 8, 6, 7
};
cout<<lis(A, 6)<<endl;
return 0;
}
股票買賣:兩次交易
牛客網oj:風口上的豬
參考LeetCode題解
問題: 已知n天的股票走勢price[n], 求通過買賣兩次可以獲得的最大利潤,要求是買股票時不能持有股票.
分析:
- 簡單方法:
記至進行一次買賣,在0~i天內進行買賣,可獲得的最大收益是benefit(0,i);
則原問題答案即:max{ benefit(0,i)+benefit(i+1,n) }, 0<=i<n;
復雜度高為O(n^2);
benefit(0,n-1)的求解方法如下:
分析子問題; //在0~i天內進行yic買賣,可獲得的最大收益是benefit(0,i)
給出已知子問題的解; // benefit(0) = 0; benefit(1) = max{0,price[1]-price[0]};
-
嘗試推出下一個子問題;
minPrice = min{price[1], price[0]} //當前最低價
benefitTemp = price[2] - minPrice
//與之前的策略比較,設置當前最大收益
if benefitTemp > benefit(0,1) : benefit(0,2) = benefitTemp
else : benefit(0,2) = benefit(0,1) ;
if benefitTemp < 0 : minPrice = price[0,2];//更新當前最低價
分析子問題的狀態轉移函數;
minPrice = min { price[0],...,price[i]);
benefit(0,i) = max{ price[2] - minPrice, benefit( 0, i - 1 ) };
- 雙向求解:
上方法有重復求解的問題,可以通過2次遍歷來解決- 遍歷求解在i天及之前進行一次買賣,可獲得的最大收益 benefit(0,i), 0<=i<n;
- 遍歷求解在i天及之后進行一次買賣,可獲得的最大收益 benefit(i,n-1), 0<=i<n;
- 遍歷 benefit(0,i)+ benefit(i+1,n-1), 即可求出最大收益
class Solution {
vector<int> saleMax;
vector<int> buyMax;
public:
Solution(){
saleMax.reserve(100);
buyMax.reserve(100);
}
/**
* 計算你能獲得的最大收益
*
* @param prices Prices[i]即第i天的股價
* @return 整型
*/
int calculateMax(vector<int> prices) {
int len = prices.size();
if(len < 2) return 0;
int temp(0),minPrice(0),maxPrice(0);
//第i天及之前買賣可以獲得的最大利益
saleMax.resize(len);
saleMax[0]=0;
minPrice = prices[0];
for(int i=1;i<len;i++) {
temp = prices[i]-minPrice;
minPrice = (temp<0)?prices[i]:minPrice;
saleMax[i] = (temp>0)?temp:0;
}
//從第i天可以買入,則可以獲得的最大利益
int maxBenifit(0);
buyMax.resize(len);
maxPrice = prices[len-1];
for(int i=len-1;i>=0;i--){
temp = maxPrice-prices[i];
maxPrice = (temp<0)?prices[i]:maxPrice;
maxBenifit = (temp>maxBenifit)?temp:maxBenifit;
buyMax[i] = maxBenifit;
}
maxBenifit = 0;
for(int i=0;i<len;i++) {
temp = saleMax[i]+buyMax[i];
maxBenifit = (temp>maxBenifit)?temp:maxBenifit;
}
return maxBenifit;
}
};
其他問題的代碼
- 二維矩陣路線選擇
問題:平面上有N*M個格子,每個格子中放著一定數量的蘋果。你從左上角的格子開始, 每一步只能向下走或是向右走,每次走到一個格子上就把格子里的蘋果收集起來,這樣下去,你最多能收集到多少個蘋果。
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const unsigned N = 4;
const unsigned M = 8;
int main() {
//(0,0)->(2,0)->(2,5)->(3,5)->(3,7)
// 54;
unsigned apple[N*M] = {
1, 3, 2, 1, 3, 5, 8, 9,
4, 3, 1, 5, 2, 1, 1, 1,
3, 6, 1, 7, 9, 2, 1, 1,
6, 1, 1, 1, 1, 5, 7, 9
};
unsigned maxNum[N*M];
maxNum[0]=apple[0];
queue<unsigned> qu;
qu.push(0);
while (!qu.empty() ) {
unsigned curIndex = qu.front();
qu.pop();
unsigned n = curIndex/M; // (0,0) 0/M = 0; (0,7) 7/M = 0; (1,0) 8/M = 1 (1,7) 15/8 = 1
unsigned m = curIndex%M; // (0,0) 0%M = 0; (0,7) 7/M = 0; (1,0) 8/M = 0 (1,7) 15%8 = 7;
if(m<M-1) {
int r = curIndex+1;
unsigned upOne = (n==0)?0 :maxNum[r-M];
maxNum[r] = apple[r] + max<unsigned>(maxNum[curIndex], upOne);
qu.push(r);
}
if(m==0&&n<N-1) {
int r = curIndex+M;
maxNum[r] = maxNum[curIndex] + apple[r];
qu.push(r);
}
}
cout <<maxNum[N*M-1]<<endl;
return 1;
}