跟着Carl学算法--动态规划【5】
买卖股票的最佳时机
力扣链接:买卖股票的最佳时机
题目:给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
思路:
从上一题开始,每一个结点都有了多个状态
这里,今天的这个”结点“获得最大收益的有两种状态:今天持有股票或者今天不持有股票的最大收益,都取决于前一天的持有股票或不持有股票的最大收益。这也是之后股票模板题的套路
-
dp[i][1/0]
代表第i天持有或者不持有股票的最大收益,为了对应股价数组,天数i也从0开始计数 -
递推式:
-
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
-
dp[i][1] = max(dp[i - 1][1], 0 - prices[i]);
今天不持有股票可能是昨天就不持有股票继承下来的,或者昨天持有股票今天出售了导致的,所以今天不持有股票的最大收益是昨天不持股票的最大收益和昨天持有股票的最大收益加上今天出售股票的收益的最大值
同理,今天持有股票可能是昨天就持有股票继承下来的,或者昨天不持有股票今天买入了导致的,所以今天持有股票的最大收益是昨天持股票的最大收益和昨天不持有股票的最大收益减去今天购买股票的投入的最大值,注意因为此题只能买一次,所以昨天不持有股票的最大收益就是0。
-
-
初始化:初始
dp[0][0]
=0和dp[0][1]
=-prices[0];
class Solution {
public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(), vector(2, 0));// 0代表不持有,1代表持有dp[0][0] = 0;dp[0][1] = -prices[0];for (int i = 1; i < prices.size(); i++) {dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);dp[i][1] = max(dp[i - 1][1], 0 - prices[i]);}return max(dp[prices.size() - 1][0], dp[prices.size() - 1][1]);}
};
仔细想想,如果持有股票只有一种情况,对于任意一天,其实肯定是未持有股票收益高于持有股票的,最后返回未持有股票的情况即可,事实证明也是如此。
买卖股票的最佳时机II
力扣链接:买卖股票的最佳时机II
题目:给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
思路:
与上一题的唯一区别,就是可以多买多卖,只需要改变持有递推式的买入部分即可
dp[i][1] = max(dp[i - 1][1], 0 - prices[i]);
改为dp[i][1] = max(dp[i - 1][1], dp[i-1][0] - prices[i]);
,前一天未持有股票的最大收益因为多买多卖,不再是固定的0了。
class Solution {
public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(), vector(2, 0));// 0代表不持有,1代表持有dp[0][0] = 0;dp[0][1] = -prices[0];for (int i = 1; i < prices.size(); 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 max(dp[prices.size() - 1][0], dp[prices.size() - 1][1]);}
};
买卖股票的最佳时机含手续费
力扣链接:买卖股票的最佳时机含手续费
题目:给定一个整数数组 prices
,其中 prices[i]
表示第 i
天的股票价格 ;整数 fee
代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
思路:
与上一题的区别,就是卖出时要减去手续费即可,只需要改变未持有递推式的卖出部分即可
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
改为dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]-fee);
class Solution {
public:int maxProfit(vector<int>& prices,int fee) {vector<vector<int>> dp(prices.size(), vector(2, 0));// 0代表不持有,1代表持有dp[0][0] = 0;dp[0][1] = -prices[0];for (int i = 1; i < prices.size(); i++) {dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);}return max(dp[prices.size() - 1][0], dp[prices.size() - 1][1]);}
};
买卖股票的最佳时机含冷冻期
力扣链接:买卖股票的最佳时机含冷冻期
题目:给定一个整数数组prices
,其中第 prices[i]
表示第 *i*
天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:
与买卖股票II的区别,就是卖出以后会有冷冻期一天,不能买入,也就是说买入会受到影响,所以只要改变持有递推式的买入部分就可以了
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
改为dp[i][1] = max(dp[i - 1][1], dp[i - 2][0] - prices[i]);
因为买入时,要确保今天不是冷冻期,仅对前一天状态进行约束是不行的,前一天未持有,也可能是卖出去了,那今天就是冷动期了,至少得对前两天就进行约束,即前两天就未持有了,就算前两天卖出了,到今天也刚好冷冻期过了,- 同时,因为递推式中出现了i-2,所以初始化时,就需要对前两项进行初始化,同时也要判断给出的股票数是否大于1。
class Solution {
public:int maxProfit(vector<int>& prices) {if (prices.size() == 1)return 0;vector<vector<int>> dp(prices.size(), vector(2, 0));dp[0][0] = 0;dp[0][1] = -prices[0];dp[1][1] = max(dp[0][1], dp[0][0] - prices[1]);dp[1][0] = max(dp[0][0], dp[0][1] + prices[1]);for (int i = 2; i < prices.size(); 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 max(dp[prices.size() - 1][0], dp[prices.size() - 1][1]);}
};
买卖股票的最佳时机III
力扣链接:买卖股票的最佳时机III
题目:给定一个数组,它的第 i
个元素是一支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:
与买卖股票II类似,但是至多买卖股票数不能超过两次,如何确保不会超过两次?
可以进一步细分状态,将持有细化为持有第一次和第二次,未持有细化为未持有第一次和未持有第二次,再额外增加以一个未操作,来标记第一次买入前的状态,这样每一个状态就都包含了,
dp[i][0/1/2/3/4]
,分别代表未操作,第一次持有。。。。第二次未持有- 初始化:第0天所有未持有的状态都是0,持有的都是-prices[0]
- 递推式:要么继承前一天的同一个状态,要么取决于前一天的上一个状态。这里每一天的未操作状态始终为0,不需要递推更新
class Solution {
public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(), vector(5, 0));dp[0][0] = 0;dp[0][1] = -prices[0];dp[0][2] = 0;dp[0][3] = -prices[0];dp[0][4] = 0;for (int i = 1; i < prices.size(); i++) {dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);}return max(dp[prices.size() - 1][2], dp[prices.size() - 1][4]);}
};
买卖股票的最佳时机IV
力扣链接:买卖股票的最佳时机IV
题目:给你一个整数数组 prices
和一个整数 k
,其中 prices[i]
是某支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k
笔交易。也就是说,你最多可以买 k
次,卖 k
次。
**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:
相较于上一题,明确的至多两次买卖改为了不确定的k次,思路与递推式都与III相同,只要将III抽象成模板即可。
-
dp[i][0/.../2*k]
:分别代表未操作、第一次持有。。。。第k次未持有,至多k次买卖,一次买卖需要2个状态,再加上未操作,一共2*k+1个状态。 -
初始化:对第0天的状态%2,偶数的都是未持有,奇数的都是持有,未持有即为0,持有即为-prices[0]
-
递推式:未操作不进行递推
if (j % 2 == 1) //奇数代表持有,如果今天买入,那就是前一天本次未持有最大收益-今天股票价格dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]); else //偶数代表未持有,如果今天卖出,那就是前一天上一次持有最大收益+今天股票价格dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i]);
今天的某个状态j,要么继承前一天的同一个状态,要么取决于前一天的上一个状态。
class Solution {
public:int maxProfit(int k, vector<int>& prices) {int state = 2 * k + 1;vector<vector<int>> dp(prices.size(), vector(state, 0));for (int i = 0; i < state; i++) {if (i % 2 == 1)dp[0][i] = -prices[0];}for (int i = 1; i < prices.size(); i++) {for (int j = 1; j < state; j++) // 偶数代表不持有,奇数代表持有if (j % 2 == 1)dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]);elsedp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i]);}int maxp = 0;for (int i = 2; i < state; i += 2)maxp = max(dp[prices.size() - 1][i - 2], dp[prices.size() - 1][i]);return maxp;}
};