小唐龙的炒股之旅
无限次交易:
题目链接:
你问我为啥不先说买卖股票一,你先别急,等会给你讲。咱先看二。
这里,dp[0][i]
表示第 i
天结束时手上没有股票时的最大利润,而 dp[1][i]
表示第 i
天结束时手上有股票时的最大利润。
首先定义一个二维dp数组长度分别为2和len(原一维数组的长度)
-
对于
dp[0][i](手上没有股票):
- 你可以在第
i
天不进行任何操作(即保持手上没有股票),那么你的利润就是前一天没有股票时的利润,即dp[0][i-1]
。 - 或者,你可以在第
i
天卖出股票(前提是你前一天手上有股票),那么你的利润就是前一天手上有股票时的利润加上今天卖出的价格,即dp[1][i-1] + prices[i]
。
所以,dp[0][i]
的值就是这两者中的较大值。
- 你可以在第
-
对于
dp[1][i](手上有股票):
- 你可以在第
i
天不进行任何操作(即保持手上有股票),那么你的利润就是前一天手上有股票时的利润,即dp[1][i-1]
。 - 或者,你可以在第
i
天买入股票(前提是你前一天手上没有股票),那么你的利润就是前一天没有股票时的利润减去今天买入的价格,即dp[0][i-1] - prices[i]
。但是,由于买入股票会产生负利润(因为你付出了钱),所以这里我们只关心dp[0][i-1] - prices[i]
是否大于dp[1][i-1]
(即,我们是否应该在这一天买入股票)。
所以,dp[1][i]
的值就是这两者中的较大值(但实际上,由于买入股票会产生负利润,所以大多数情况下,我们不会选择在第i
天买入股票,除非prices[i]
非常低)。
- 你可以在第
最后,返回 dp[0][len - 1]
,即最后一天结束时手上没有股票时的最大利润,这就是你可以获得的最大利润。你问为啥不是dp[1][len - 1]
?最后一天买了股票你赚啥,对吧。
来,贴ac代码:
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if(len == 0){
return 0;
}
int[][] dp = new int[2][len];
dp[0][0] = 0;
dp[1][0] = -prices[0];
for(int i = 1; i < len; i++){
dp[1][i] = Math.max(dp[1][i - 1], dp[0][i - 1] - prices[i]);
dp[0][i] = Math.max(dp[0][i - 1], dp[1][i - 1] + prices[i]);
}
return dp[0][len - 1];
}
}
手续费啊,改一下状态转移方程不就好了
dp[0][i] = Math.max(dp[1][i - 1] + prices[i] - fee, dp[0][i - 1]);
dp[1][i] = Math.max(dp[1][i - 1], dp[0][i - 1] - prices[i]);
提交,ac,完美。
冷冻期啊,这个嘛,不就是卖出的第二天不能交易嘛。
状态转移方程一改变不就好了嘛。
dp[0][i] = Math.max(dp[0][i - 1], dp[1][i - 1] + prices[i]);
dp[1][i] = Math.max(dp[0][i - 2] - prices[i], dp[1][i - 1]);
再将边界处理好,不就是处理原数组长度为1和2嘛。
来看代码:
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[2][len];
dp[0][0] = 0;
dp[1][0] = -prices[0];
if(len == 0 || len == 1){
return 0;
}
if(len == 2){
if(prices[1] > prices[0]){
return prices[1] - prices[0];
}
return 0;
}
dp[0][1] = Math.max(0, prices[1] - prices[0]);
dp[1][1] = Math.max(-prices[0], -prices[1]);
for(int i = 2; i < len; i++){
dp[0][i] = Math.max(dp[0][i - 1], dp[1][i - 1] + prices[i]);
dp[1][i] = Math.max(dp[0][i - 2] - prices[i], dp[1][i - 1]);
}
return dp[0][len - 1];
}
}
其实相应的,这段代码改一下,还可以处理冷冻期为k天的情况,状态转移方程即为:
dp[0][i] = Math.max(dp[0][i - 1], dp[1][i - 1] + prices[i]);
dp[1][i] = Math.max(dp[0][i - k + 1] - prices[i], dp[1][i - 1]);
那你问怎么处理边界条件呢,k要是大点怎么办,那么前面的边界问题不就转化为限定一次的交易了?那就先看下面的问题。
有限次交易:(最多k次)
咱们一起来看一和三,有什么相似之处,和二又有什么相差之处呢?
哎对了,一和三都是有限次数交易,二则是无限次数交易。那有没有啥能直接两道一起ac了的代码呢?你还别说,真没有。但是有一个只改了一点的。
请看:
public int numsKmaxProfit(int[] prices, int k) {
int len = prices.length;
if(len ==0){
return 0;
}
int [][][] dp = new int [2][k + 1][len];
for (int j = 0; j <= k; j++) {
dp[0][j][0] = 0;
if (j > 0) {
dp[1][j][0] = -prices[0];
}
}
for (int i = 1; i < len; i++) {
for (int j = 1; j <= k; j++) {
dp[0][j][i] = Math.max(dp[0][j][i - 1], dp[1][j][i - 1] + prices[i]);
dp[1][j][i] = Math.max(dp[1][j][i - 1], j > 0 ? dp[0][j - 1][i - 1] - prices[i] : Integer.MIN_VALUE);
}
}
int max = 0;
for (int j = 0; j <= k; j++) {
max = Math.max(max, dp[0][j][len - 1]);
}
return max;
}
这是啥,这是至多k次的。一的话就直接
return numsKmaxProfit(prices,1);
三的话就这个:
return numsKmaxProfit(prices,2);
怎么样,是不是只改了一点点。
来,大概解释一下吧。
- 初始化:
len
:股票价格的长度。k
:允许的最大交易次数。dp
:三维动态规划数组,其中dp[i][j][t]
表示在第t
天结束时,进行了j
次交易,并且当前状态为i
(0表示不持有股票,1表示持有股票)时的最大利润。
- 边界条件:
- 对于任何
j
(交易次数),第一天不持有股票的最大利润都是0(dp[0][j][0] = 0
)。 - 对于
j > 0
(即进行了至少一次交易),第一天持有股票的最大利润是-prices[0]
(因为你买入了股票但还没有卖出)。
- 对于任何
- 动态规划状态转移:
- 遍历每一天t(从第二天开始)和每一种可能的交易次数j:
- 如果不持有股票(i=0),则有两种情况:
- 这一天没有进行任何交易,利润与前一天相同(
dp[0][j][i-1]
)。 - 这一天卖出了股票(注意这里不需要判断
j
是否大于0,因为卖出股票不消耗交易次数),利润是前一天持有股票的利润加上今天的卖出价格(dp[1][j][i-1] + prices[i]
)。
- 这一天没有进行任何交易,利润与前一天相同(
- 如果持有股票(i=1),则有两种情况:
- 这一天没有进行任何交易,利润与前一天相同(
dp[1][j][i-1]
)。 - 这一天买入了股票(注意这里需要判断
j
是否大于0,因为买入股票消耗一次交易次数),利润是前一天不持有股票且交易次数减一的利润减去今天的买入价格(dp[0][j-1][i-1] - prices[i]
)。
- 这一天没有进行任何交易,利润与前一天相同(
- 如果不持有股票(i=0),则有两种情况:
- 遍历每一天t(从第二天开始)和每一种可能的交易次数j:
- 找出最终结果:
- 遍历最后一天(
len-1
)的所有可能交易次数j
,并找出不持有股票时的最大利润。
- 遍历最后一天(
时间复杂度是:O(k * len)
那再看股票四:
什么,这不就是最多交易k次的吗!!!,那上面咱们的代码可不可以ac呢,当然了,当然还需要把咱们的函数改成原来的函数名。
那么能ac吗,那当然是可以的了。
[外链图片转存中…(img-4y2QbfMk-1715513902366)]
什么,这不就是最多交易k次的吗!!!,那上面咱们的代码可不可以ac呢,当然了,当然还需要把咱们的函数改成原来的函数名。
那么能ac吗,那当然是可以的了。
其实股票系列这一块,我在做完两次交易之后(当然第一次的代码肯定不如这篇里的难),就突然发现股票一好像和股票三差不了多少,然后把三的数值改了改拿去一测试,发现可以过,就想写一个k次的(当然还没看到股票四的题),emm,于是写完了,再去看股票四,发现这不就是我写过的,复制,粘贴,ac,美滋滋。