动态规划

几篇很好的资料

动态规划问题

  • 最长非降子序列
    一个序列有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];
  • 总结
  1. 分析子问题; //v[0]~v[i] 的最长非降子序列s(i)

  2. 给出已知子问题的解; // s(0)= {5}

  3. 尝试推出下一个子问题;


    v[1]<s0.max => s(1) = {3}


    v[2]>s1.max: {3 4}
    v[2]<s0.max: {4} => s(2) = {3,4}


  4. 分析子问题的状态转移函数;
    s(i) = longest{ v[i], s(j), s(k),... }
    其中 0<=k<j< i , 且 v[i]>= s(k).max, v[i] >= s(j).max

  5. 整理以上思路即可;

//摘自[动态规划-新手到专家]
#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)的求解方法如下:
  1. 分析子问题; //在0~i天内进行yic买卖,可获得的最大收益是benefit(0,i)

  2. 给出已知子问题的解; // benefit(0) = 0; benefit(1) = max{0,price[1]-price[0]};

  3. 尝试推出下一个子问题;


    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];//更新当前最低价


  4. 分析子问题的状态转移函数;
    minPrice = min { price[0],...,price[i]);
    benefit(0,i) = max{ price[2] - minPrice, benefit( 0, i - 1 ) };

  • 双向求解:
    上方法有重复求解的问题,可以通过2次遍历来解决
    1. 遍历求解在i天及之前进行一次买卖,可获得的最大收益 benefit(0,i), 0<=i<n;
    2. 遍历求解在i天及之后进行一次买卖,可获得的最大收益 benefit(i,n-1), 0<=i<n;
    3. 遍历 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;
}
小鹅双拼.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 本文翻译自TopCoder上的一篇文章: Dynamic Programming: From novice to ...
    扎Zn了老Fe阅读 1,920评论 0 3
  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,621评论 0 89
  • 分治方法 将问题划分成互不相交的子问题 递归地求解子问题 将子问题的解组合起来 动态规划(两个要素:最优子结构、子...
    superlj666阅读 491评论 0 0
  • 1.不知道为什么,明明点了蚊香,却依然被叮了好多下,莫非蚊子已经对蚊香免疫了?都说秋天的蚊子特别厉害,俗称“秋老虎...
    尘世书童阅读 191评论 0 1
  • 鸿记煌三汁焖锅浪漫之旅!芦笙向来就是一个敏感的人,可能是高考的压力过大或者说是自己自小跟着爷爷奶奶,父母工作繁忙没...
    未闻风音阅读 196评论 0 0