非模板(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;