动态规划-股票买卖问题

labuladong股票买卖

非模板(k=1只需一维数组)

在这里插入图片描述

1.定义dp[i]

dp[i]表示第i天所能获得的最大利润

2.状态转移方程

当price[i]<=price[i-1]时:dp[i]=dp[i-1]
当price[i]>price[i-1]时:dp[i]=price[i]-min(price[0~i-1])
这里如果要找min(price[0~i-1])可以for循环遍历,但是这样时间复杂度会变成$O(n^2),一个优化时间复杂度的方法是用一个变量min_price维护目前的最低股价即可

模板法(三维数组 )

1.定义dp数组和状态转移方程

在这里插入图片描述

2.base case
  • k=0的时候
    dp[i][0][0] 表示前i天不能进行交易,并且第i天不持股,那么利润=0
    dp[i][0][1] 表示前i天不能进行交易,并且第i天持股,这不可能发生,那么用 -INF表示

  • i=0~n-1,那么计算i=0的时候,会出现i-1=-1的情况:
    dp[-1][k][0] 表示开始交易的前一天,不持股,前一天最大利润为0
    dp[-1][k][1] 表示开始交易的前一天就持股了,这是不可能的可以用-INF表示,也可以用-price[0]表示

3.k=1/INF的时候

当k=1/INF的时候,可以发现k的值对装填转移方程没有影响,可以将dp降至二维dp[i][0]和dp[i][1],并且递推法只需要for循环天数(i),因为k消除,0/1直接写在方程里,都不需要循环
因此时间复杂度为 O ( n ) O(n) O(n)

模板状态转移方程(最多进行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])
(1) k=1时,k-1=0

dp[i-1][k-1][0]=dp[i-1][0][0]=0代入上式,

可以发现,转台转移方程中,k的值没有变化,所以k值不影响状态转移,可以省略k,化简得到:

dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i])
dp[i][1]=max(dp[i-1][1],-prices[i])
(2)k=INF时

k无限,那么k和k-1没什么区别,可以把k去掉,化简状态转移方程如下:

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])

代码如下:

int dp[n][2];
int 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];

(3)空间优化

可以看到,当化简后的状态转移方程,只用到两个变量(和他们的各自相邻一天的变量)

dp[i][k][0]和dp[i][k][1],他们对应相邻的两个变量:dp[i-1][k][0]和dp[i-1][k][1],那么可以用两个变量代替数组存储这两个变量:

int i_0;//表示dp[i][k][0],和dp[i-1][k][0]
int i_1;//表示dp[i][k][1],和dp[i-1][k][1]

状态转移方程可以写成:

int i_0=0,i_1=-prices[0];
for(int i=1;i<n;i++)
{
	i_0=max(i_0,i_1+prices[i]);
	i_1=max(i_1,i_0-prices[i]);
}
4.k>=2时

当k>=2的时候,维数不可以省略,并且需要用for循环遍历k值(1~max_k),因此时间复杂度为 O ( K n ) O(Kn) O(Kn)

需要注意的是,当k可能>=2的时候
base case分别对i=0和k=0的时候赋值
遍历k值的时候从大到小,k的最小值为1

    for(int i=0;i<n;i++)//k的base case
    {
        dp[i][0][0]=0;
        dp[i][0][1]=-INF;
    }
 for(int i=0;i<n;i++)
    for(int l=k;l>=1;l--)
    {
        if(i==0) //i的base case
        {
            dp[0][l][0]=0;
            dp[0][l][1]=-prices[i];
     	}
        else
        {
            dp[i][l][0]=max(dp[i-1][l][0],dp[i-1][l][1]+prices[i]);
            dp[i][l][1]=max(dp[i-1][l][1],dp[i-1][l-1][0]-prices[i]);
        }
    }
5.存在交易冷冻期的时候

今天卖出,明天不能买入,后天才可以买入,那就修改dp[i][k][1]为:

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

代码如下:

//int dp[n][2];
//base case
//dp[0][0]=0;
//dp[0][1]=-prices[0];
int i_0=0,i_1=-prices[i];
int i_pre=0;//表示dp[i-2][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-2][0]-prices[i]);

int tmp=i_0;//假设i=t,此时tmp相当于dp[t-1][0];
i_0=max(i_0,i_1+prices[i]);//此时等号左边是dp[t][0]
i_1=max(i_1,i_pre-prices[i]);//此时等号右边是dp[t][1],等号右边的i_pre是上一轮for循环i=t-1时保留的tmp的值dp[t-2][0]
i_pre=tmp;//此时i_pre中保留的是dp[t-1][0]
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值