1. 程式人生 > >[LeetCode]動態規劃中股票問題的通用解法

[LeetCode]動態規劃中股票問題的通用解法

動態規劃中股票問題的通用解法

有一類動態規劃的問題是給定一個股票價格序列,然後計算買賣股票所能獲得的最大收益,這類問題通常有很多變種,例如只允許交易一次,允許交易多次或者增收交易稅等。即問題的最大收益通常由交易的時間和允許的最大交易次數(每次交易指一次買與一次賣的一個組合)決定的。

可以用T[i][k]表示在第i天結束的時候最多經過k次交易所能獲得的最大收益,另外在第i天結束的時候可以有兩種狀態,手上有股票T[i][k][1]或者手上沒有股票T[i][k][0],可以得到以下初始狀態:

T[-1][k][0] = 0, T[-1][k][1] = -Infinity
T[i][0][0] = 0
, T[i][0][1] = -Infinity

其中T[-1][k][0] = 0與T[i][0][0] = 0意味著在初始狀態下(i=-1)即沒有股票的時候收益為0,在最多允許0次交易的情況下收益也為0。而T[-1][k][1]=T[i][0][1] = -Infinity意味著在初始情況下手裡有股票以及允許0次交易的情況下手裡有股票是不可能的,將其收益設為-Infinity。

而對於狀態的轉移,我們可以得到以下的公式

T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])
//對於在第i天結束的時候手裡沒有股票的情況下,在這一天所能採取的操作有兩種
//1.不進行交易,即最大收益為T[i-1][k][0]
//2.進行賣出,即第i天的最大收益為上一天有股票時的最大收益加上當天的賣價為T[i-1][k][1] + prices[i] T[i][k][1] = max(T[i-1][k][1], T[i-1][k-1][0] - prices[i]) //在第i天結束手裡有股票時,同樣有兩種操作: //1.不進行交易,即為T[i-1][k][0] //2.進行買入,這時的買入增加了一次交易次數,那麼i-1天結束的最大交易次數為k-1,即當天結束的最大收益為T[i-1][k-1][0] - prices[i]

由於收益最多的時候最後一步應該是將股票賣出,即最後返回的應該為T[i][k][0]

LeetCode裡面相關的經典題目

Best Time to Buy and Sell Stock
這道題目是求只允許一次交易的情況下所能獲得的最大收益,即k=1的情況,其解法如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int T_i10=0,T_i11=INT_MIN;
        for(int price:prices){
            T_i10=max(T_i10,T_i11+price);
            T_i11=max(T_i11,-price);
        }
        return T_i10;
    }
};

Best Time to Buy and Sell Stock II
這道題目是不限制交易次數的情況,即k=Infinity的時候,此時k-1=k,那麼狀態轉移方程可以寫成如下形式:

T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])
T[i][k][1] = max(T[i-1][k][1], T[i-1][k-1][0] - prices[i]) = max(T[i-1][k][1], T[i-1][k][0] - prices[i])

因為T[i][k][0]在前一步進行了更新,那麼可以利用一個臨時變數來儲存T[i][k][0],其程式碼如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int T_ik0=0,T_ik1=INT_MIN;
        for(int price:prices){
            int T_ik0_old=T_ik0;
            T_ik0=max(T_ik0,T_ik1+price);
            T_ik1=max(T_ik1,T_ik0_old-price);
        }
        return T_ik0;
    }
};

Best Time to Buy and Sell Stock III
這道題目是找在限制最大交易次數為2的情況下所能獲得的最大收益,此時k=2或k=1,與k=1的情況類似,其狀態轉移方程可以寫成以下形式:

T[i][2][0] = max(T[i-1][2][0], T[i-1][2][1] + prices[i])
T[i][2][1] = max(T[i-1][2][1], T[i-1][1][0] - prices[i])
T[i][1][0] = max(T[i-1][1][0], T[i-1][1][1] + prices[i])
T[i][1][1] = max(T[i-1][1][1], -prices[i])

最後返回的最優解即為T[i][k][0]=T[i][2][0],完整解法如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int T_i10=0,T_i11=INT_MIN;
        int T_i20=0,T_i21=INT_MIN;
        for(int price:prices){
            T_i20=max(T_i20,T_i21+price);
            T_i21=max(T_i21,T_i10-price);
            T_i10=max(T_i10,T_i11+price);
            T_i11=max(T_i11,-price);
        }
        return T_i20;
    }
};

Best Time to Buy and Sell Stock IV
這道題目是將k作為引數傳入求解函式裡面,可以看出來每一次能夠獲得收益的交易都是需要兩天時間的,那麼當k>=n/2的時候再增加k也不會增加收益,那麼此時可以看作是k=Infinity,這種情況與前面的問題相同。當k < n/2的時候則跟k=2的情況類似,可以通過建立陣列的方式來向前更新。解法如下:

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if(k>=prices.size()>>1){
            int T_ik0=0,T_ik1=INT_MIN;
            for(int price:prices){
                int T_ik0_old=T_ik0;
                T_ik0=max(T_ik0,T_ik1+price);
                T_ik1=max(T_ik1,T_ik0_old-price);
            }
            return T_ik0;
        }
        vector<int> T_ik0(k+1,0);
        vector<int> T_ik1(k+1,INT_MIN);
        for(int price:prices){
            for(int j=k;j>0;j--){
                T_ik0[j]=max(T_ik0[j],T_ik1[j]+price);
                T_ik1[j]=max(T_ik1[j],T_ik0[j-1]-price);
            }
        }
        return T_ik0[k];
    }
};

Best Time to Buy and Sell Stock with Cooldown
這道題目並沒有限制k的取值,即為Infinity,但是有另外一個限制就是不能在前一天賣掉股票然後第二天又馬上買入,起到一個cooldown的作用。那麼在這個狀態轉移方程中,對於T[i][k][1]的更新會有所不同,在第i天採取休息的情況下是相同的,在採取買入的操作的時候更新為T[i-2][k-1][0]=T[i-2][k][0],其狀態轉移方程如下:

T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])
T[i][k][1] = max(T[i-1][k][1], T[i-2][k][0] - prices[i])

該題目的程式碼為:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int T_ik0_pre=0,T_ik0=0,T_ik1=INT_MIN;
        for(int price:prices){
            int T_ik0_old=T_ik0;
            T_ik0=max(T_ik0,T_ik1+price);
            T_ik1=max(T_ik1,T_ik0_pre-price);
            T_ik0_pre=T_ik0_old;
        }
        return T_ik0;
    }
};

Best Time to Buy and Sell Stock with Transaction Fee
這道題目與之前的不同在於,每次交易都要增收一定額度的交易稅,而同樣也是不限制交易次數的,這個可以在每次交易的時候(賣或者買的時候都可以)減去交易稅的金額,然後再更新相應的最大收益T[i][k][0]或者T[i][k][1],其狀態轉移方程如下:

T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])
T[i][k][1] = max(T[i-1][k][1], T[i-1][k][0] - prices[i] - fee)

or

T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i] - fee)
T[i][k][1] = max(T[i-1][k][1], T[i-1][k][0] - prices[i])

演算法程式碼如下:

#include<climits>
class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        long T_ik0=0,T_ik1=LONG_MIN;
        for(int price:prices){
            long T_ik0_old=T_ik0;
            T_ik0=max(T_ik0,T_ik1+price);
            T_ik1=max(T_ik1,T_ik0_old-price-fee);
        }
        return (int)T_ik0;
    }
};