几篇很好的资料
动态规划问题
- 最长非降子序列
一个序列有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;
}