动态规划——买卖股票最佳时机

今天在leetcode上刷题时,遇到了这个题目,看到有一个题解写的非常好,提出一种方法能够解决全部的买卖股票的相关问题(在leetcode中分别为121,122,123,188,309,714),现将其整理出来,并加入一些自己的理解。文章参考自:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/yi-ge-fang-fa-tuan-mie-6-dao-gu-piao-wen-ti-by-l-3/

方法介绍

首先,我们要明确 买+卖为一次交易

定义状态dp[i][k][0 or 1],表示在第i天,至今最多进行j次交易并且手中持有or没有股票所能获得的最大收益,其中,0<=i<=n-1, 1<=j<=K(K为最多交易次数)。
例如dp[3][2][1]的含义就是:在第3天,至今最多进行2次交易手中持有股票所能获得的最大收益。
dp[2][3][0]的含义就是:在第2天,至今最多进行3次交易,手中没有持有股票所能获得的最大收益。
状态转移方程

dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);
//解释:今天我没有持有股票,有两种可能
//1、要么是我昨天就没有持有,今天选择了rest,所以我今天还是没有持有
//2、要么时我昨天持有股票,但是今天选择了卖出,所以我今天没有持有

dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);
//解释:今天我持有股票,有两种可能
//1、要么是我昨天就持有,今天选择了rest,所以我今天还是持有
//2、要么时我昨天没有持有股票,但是今天选择了买入,所以我今天持有

在这个地方,我当时存在一些疑问,为什么第一个式子不是

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

后来通过评论区的评论,再结合我自己的理解,想出了这样一种解释。
首先,要明确题目中定义的时进行一次完成的买卖操作为一次交易,所以,你可以定义先买后卖为一次交易,也可以定义先卖后买为一次交易,两种定义选其一,因为一次交易只能被记录一次。如果选择先买后卖,那么就是当进行买入操作时即开启了一次新的交易,也就是上面的状态转移方程,即

dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);//卖
dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);//买

如果选择先卖后买,那么就是当进行卖出操作时即开启了一次新的交易,状态转移方程就是

dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k-1][1]+prices[i]);//卖
dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k][0]-prices[i]);//买

但是,如果采用此种定义方法,意味着你在一开始进行的时卖出操作,但是在手上没有股票的时候,是无法进行卖出操作的,所以定义方式在leetcode上无法通过
初始化

dp[-1][k][0] = 0;//i从0开始,当i=-1时,因为这交易还没有开始,最大利润为0
dp[-1][k][1] = -INT_MAX;//交易开始,手中不可能持有股票
dp[i][0][0] = 0;//允许最大交易次数为0,最大利润为0
dp[i][0][1] = -INT_MAX;//允许最大交易次数为0.手中不可能持有股票

解题

方法介绍完了,接下来就用着这方法解决6道股票买卖问题。

121-只允许完成一次交易,k=1

分析

dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);//卖
dp[i][1][0] = max(dp[i-1][1][0],dp[i-1][1][1]+prices[i])

dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);//买
dp[i][1][1] = max(dp[i-1][1][1],dp[i-1][0][0]-prices[i])
			= max(dp[i-1][1][1],-prices[i])
//可以发现k都是1,不会改变,即k对状态转移已经没有影响了

代码实现

int maxProfit(vector<int>& prices) {
	if(prices.size() == 0)
		return 0;
	int n = prices.size();
	int dp[n][2];
	dp[0][0] = 0;
	dp[0][1] = -prices[0];
	for(int i=1;i<n;i++){
		dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i]);
		dp[i][1] = max(dp[i-1][1],-prices[i]);
	}
	return dp[n-1][0];
}

122-无交易次数限制,k=infinity

分析

dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);//卖
dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1]+prices[i])

dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);//买
dp[i][k][1] = max(dp[i-1][1][1],dp[i-1][k][0]-prices[i])
			= max(dp[i-1][1][1],dp[i-1][k][0]-prices[i])
//可以发现k由于为无限大,所以k-1和k没有区别,即k对状态转移已经没有影响了

代码实现

int maxProfit(vector<int>& prices) {
	if(prices.size() == 0)
		return 0;
	int n = prices.size();
	int dp[n][2];
	dp[0][0] = 0;
	dp[0][1] = -prices[0];
	for(int i=1;i<n;i++){
		dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i]);
		dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]);
	}
	return dp[n-1][0];
}

123-限制2次交易,k=2

分析

dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);//卖

dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);//买
//初始化
dp[0][k][0] = 0//第0天,至今最多交易k次,目前手中没有股票,所以最大利润为0
dp[0][k][1] = -prices[0]//第0天,至今最多交易k次,目前手中有股票,即最大利润为-prices[0]
dp[i][0][0] = 0//第0天,至今最多交易0次,目前手中没有股票,所以最大利润为0
dp[i][0][1] = -INT_MAX//第0天,至今最多交易0次,目前手中有股票,不可能发生,用infinity代表

代码实现

int maxProfit(vector<int>& prices) {
        if(prices.size() == 0)
            return 0;
        int n = prices.size();
        int dp[n][3][2];
        
        for(int i=0;i<n;i++){
            dp[i][0][0] = 0;
            dp[i][0][1] = -INT_MAX;
            for(int k=2;k>0;k--){
                if(i == 0){
                    dp[i][k][0] = 0;
                    dp[i][k][1] = -prices[0];
                    continue;
                }
                dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);
                dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);
            }
        }
        return dp[n-1][2][0];

    }

188-限制k次交易

分析

dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);//卖
dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);//买
//初始化
dp[0][k][0] = 0//第0天,至今最多交易k次,目前手中没有股票,所以最大利润为0
dp[0][k][1] = -prices[0]//第0天,至今最多交易k次,目前手中有股票,即最大利润为-prices[0]
dp[i][0][0] = 0//第0天,至今最多交易0次,目前手中没有股票,所以最大利润为0
dp[i][0][1] = -INT_MAX//第0天,至今最多交易0次,目前手中有股票,不可能发生,用infinity代表

另外,当k值过大时,会使k的那层循环趋向于无穷大,但是我们通过分析,因为买+卖为完整的一次交易,当k>prices.size()/2,实际相当于对于k的限制消失,即k为无穷的那种情况

代码实现

int profit(vector<int>& prices){
        int n = prices.size();
        int dp[n][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for(int i=1;i<n;i++){
            dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i]);
            dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]);
        }
        return dp[n-1][0];
    }
public:
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        if(n == 0)
            return 0;
        if(k>prices.size()/2) return profit(prices);
        int dp[n][k+1][2];
        for(int i=0;i<n;i++){
            dp[i][0][0] = 0;
            dp[i][0][1] = -INT_MAX;
            for(int j=k;j>=1;j--){
                if( i == 0){
                    dp[i][j][0] = 0;
                    dp[i][j][1] = -prices[i];
                    continue;
                }
                dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]);
                dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i]);
            }
        }
        return dp[n-1][k][0];
    }

309-不限制交易次数,有一天冷却期

分析

dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i])//卖
dp[i][1] = max(dp[i-1][1],dp[i-2][0]-prices[i]);//买
//由于限制了一天的冷却期,即卖出股票后,你无法在第二天买入股票
//dp[i][1] = max(dp[i-1][1],dp[i-2][0]-prices[i]);//买

//初始化
dp[0][0] = 0; //第0天,手里没有股票,最大收益为0
dp[0][1] = -prices[0];//第0天,手里有股票,最大收益为-prices[0]
dp[1][0] = prices[0]>prices[1]?0:prices[1]-prices[0];//第1天,手里没有股票,判断如果第0天的价格大于第1天价格,那么最大收益为prices[1]-prices[0],否则为0
dp[1][1] = -(prices[0]>prices[1]?prices[1]:prices[0]);//第1天,手里有股票,判断如果第0天的价格大于第1天价格,那么最大收益为-prices[1]否则为-prices[0]

代码实现

int maxProfit(vector<int>& prices) {
	    if(prices.size() <= 1)
		    return 0;
	    int n = prices.size();
	    int dp[n][2];
	    dp[0][0] = 0;
	    dp[0][1] = -prices[0];
        dp[1][0] = prices[0]>prices[1]?0:prices[1]-prices[0];
        dp[1][1] = -(prices[0]>prices[1]?prices[1]:prices[0]);
	    for(int i=2;i<n;i++){
		    dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i]);
		    dp[i][1] = max(dp[i-1][1],dp[i-2][0]-prices[i]);
	    }
	    return dp[n-1][0];
    }

714-不限制交易次数,有手续费

分析

dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i])//卖
dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]-fee);//买
//由于有手续费,所以每次在进行买入操作时,再股票价格的基础上还要再掏手续费
//dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]-fee);//买

//初始化
dp[0][0] = 0; //第0天,手里没有股票,最大收益为0
dp[0][1] = -prices[0]-fee;//第0天,手里有股票,最大收益为-prices[0],再掏手续费

代码实现

int maxProfit(vector<int>& prices, int fee) {
        if(prices.size() <= 1)
		    return 0;
	    int n = prices.size();
	    int dp[n][2];
	    dp[0][0] = 0;
	    dp[0][1] = -prices[0]-fee;
	    for(int i=1;i<n;i++){
		    dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i]);
		    dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]-fee);
	    }
	    return dp[n-1][0];
    }

想说的话

感谢大佬提供的解题思路,一下子解决了这一类的问题,因为自己非科班出身,所以对于算法方面不是很了解,有这样优秀的题解真是帮了大忙, 当然上面的算法并不是最优的,还有一定的优化空间,后面也会不断的学习,大家加油~
最后,想问问各位大佬,这种方法算是动态规划还是穷举呢?

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值