動態(tài)規(guī)劃

幾篇很好的資料

動態(tài)規(guī)劃問題

  • 最長非降子序列
    一個序列有N個數(shù):A[1],A[2],…,A[N],求出最長非降子序列的長度.
  • 股票買賣:一次交易
    你得到一系列在接下來幾天的股票價格,現(xiàn)在你被允許只用一次交易(就是買進(jìn)再賣出)來獲取最大利益
  • 股票買賣:兩次交易
    允許兩次買賣,但是買之前手里必須沒有股票
  • 股票買賣:多次交易
    允許 k 次買賣,但是買之前手里必須沒有股票
  • 二維矩陣路線
    平面上有N*M個格子,每個格子中放著一定數(shù)量的蘋果。你從左上角的格子開始, 每一步只能向下走或是向右走,每次走到一個格子上就把格子里的蘋果收集起來, 這樣下去,你最多能收集到多少個蘋果
  • 網(wǎng)絡(luò)節(jié)點(diǎn)間最短路徑
    給定網(wǎng)絡(luò)graph,和其內(nèi)指定節(jié)點(diǎn)v1 v2,求出v1到v2的帶權(quán)重的最短路徑.

基本思想和策略

分析 問題 可能的 子問題,記錄子問題的狀態(tài),通過子問題的狀態(tài)轉(zhuǎn)移函數(shù)間接推導(dǎo) 問題 的解.
 以 動態(tài)規(guī)劃-新手到專家"最長非降子序列"問題為例,進(jìn)行介紹

int v[] = {5,3,4,8,6,7}
//很明顯v[]的最長非降子序列是 3 4 6 7, 下面給出動態(tài)規(guī)劃的思考過程
  • 子問題
    求出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} ...
  • 狀態(tài)轉(zhuǎn)移函數(shù)
    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)利用狀態(tài)轉(zhuǎn)移函數(shù)一步步推導(dǎo)出s[n-1];
  • 總結(jié)
  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. 分析子問題的狀態(tài)轉(zhuǎn)移函數(shù);
    s(i) = longest{ v[i], s(j), s(k),... }
    其中 0<=k<j< i , 且 v[i]>= s(k).max, v[i] >= s(j).max

  5. 整理以上思路即可;

//摘自[動態(tài)規(guī)劃-新手到專家]
#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;
}

股票買賣:兩次交易

??途W(wǎng)oj:風(fēng)口上的豬
參考LeetCode題解
問題: 已知n天的股票走勢price[n], 求通過買賣兩次可以獲得的最大利潤,要求是買股票時不能持有股票.
分析:

  • 簡單方法:
    記至進(jìn)行一次買賣,在0~i天內(nèi)進(jìn)行買賣,可獲得的最大收益是benefit(0,i);
    則原問題答案即:max{ benefit(0,i)+benefit(i+1,n) }, 0<=i<n;
    復(fù)雜度高為O(n^2);
    benefit(0,n-1)的求解方法如下:
  1. 分析子問題; //在0~i天內(nèi)進(jìn)行yic買賣,可獲得的最大收益是benefit(0,i)

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

  3. 嘗試推出下一個子問題;


    minPrice = min{price[1], price[0]} //當(dāng)前最低價


    benefitTemp = price[2] - minPrice
    //與之前的策略比較,設(shè)置當(dāng)前最大收益
    if benefitTemp > benefit(0,1) : benefit(0,2) = benefitTemp
    else : benefit(0,2) = benefit(0,1) ;
    if benefitTemp < 0 : minPrice = price[0,2];//更新當(dāng)前最低價


  4. 分析子問題的狀態(tài)轉(zhuǎn)移函數(shù);
    minPrice = min { price[0],...,price[i]);
    benefit(0,i) = max{ price[2] - minPrice, benefit( 0, i - 1 ) };

  • 雙向求解:
    上方法有重復(fù)求解的問題,可以通過2次遍歷來解決
    1. 遍歷求解在i天及之前進(jìn)行一次買賣,可獲得的最大收益 benefit(0,i), 0<=i<n;
    2. 遍歷求解在i天及之后進(jìn)行一次買賣,可獲得的最大收益 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個格子,每個格子中放著一定數(shù)量的蘋果。你從左上角的格子開始, 每一步只能向下走或是向右走,每次走到一個格子上就把格子里的蘋果收集起來,這樣下去,你最多能收集到多少個蘋果。
#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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容