LeetCode309. 最佳买卖股票时机含冷冻期
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
定义状态:我们设置一个数组dp[len][2],第二个维度为1时表示此时持有股票,为0表示未持有股票
状态方程:
- dp[i][0] = max(prices[i] - dp[i - 1][1], dp[i - 1][0]); // 第i天不持有股票,此时我们来记录先前抛售利润的最大值
- case1:前一天买入股票,我们当天抛售;
- case2:前一天未买入股票。
- dp[i][1] = min(prices[i] - dp[i - 2][0], dp[i - 1][1]); // 第i天,持有股票,这时应让持有的股票值最小,这样相减时利润最大
- case1:冷却期结束也未买入(即前两天为冷却期,前一天也未买入),我们当天买入;
- case2:前一天买入股票。
边界情况:数组长度小于等于2的情况
初值定义:
- dp[0][0] = 0; // 第一天,不持有股票
- dp[0][1] = prices[0]; // 第一天,持有股票
- dp[1][0] = max(dp[0][0], prices[1] - dp[0][1]); // 第二天,不持有股票
- dp[1][1] = min(prices[1] - dp[0][0], dp[0][1]); // 第二天,持有股票
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len <= 1) return 0; // 特殊情况处理
if(len == 2 && prices[0] >= prices[1]) return 0; // 特殊情况处理
else if(len == 2 && prices[0] < prices[1])
return prices[1] - prices[0]; // 特殊情况处理
vector<vector<int>> dp(len, vector<int>(2, 0));
dp[0][0] = 0; // 第一天,不持有股票
dp[0][1] = prices[0]; // 第一天,持有股票
dp[1][0] = max(dp[0][0], prices[1] - dp[0][1]); // 第二天,不持有股票
dp[1][1] = min(prices[1] - dp[0][0], dp[0][1]); // 第二天,持有股票
for(int i = 2; i < len; i++){
dp[i][0] = max(prices[i] - dp[i - 1][1], dp[i - 1][0]);
// 第i天,不持有股票,可能此时已抛售这时应让利润最大
dp[i][1] = min(prices[i] - dp[i - 2][0], dp[i - 1][1]);
// 第i天,持有股票,这时应让持有的股票值最小
}
return dp[len - 1][0];
}
};
LeetCode123. 买卖股票的最佳时机 III
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [3,3,5,0,0,3,1,4]
输出: 6
解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
状态表示
与上一题类似用 dp(i, j) 表示第 i 天进行 j 次交易的最大利润。
状态属性
可以看到题目要求是最大利润,所以属性就是求一个最大值。
状态计算
这个是最难的一步,方法就是从 dp(i,j) 表示的 实际意义 出发,尽可能地划分出所有的条件。比如本题,我们从关键词第 i 天,第 j 次交易进行思考,不难发现第 i 天可以划分两种情况:
- 不交易:第 i 天什么都没发生,交易次数 j 不变,利润为前一天的利润:dp(i, j)=dp(i − 1, j);
- 交易:利润为前 j−1 次交易的利润与第 i 天交易的利润之和:dp(i, j)=dp(k − 1, j − 1) + prices(i) − prices(k);
由于买入卖出为一次交易,因此准确来讲如果在第 i 天进行交易就是指在第 i 天进行卖出操作,那么是何时买入的呢?如果买入为第 k 天,则 k 满足 k∈[1,...,i]。
算法
由于重复计算 k 会多嵌套一层循环导致代码超时,因此需要将第一笔交易获得的利润整合到第二笔交易的成本中。
dp(i, j)=prices(i) − (prices(k) − dp(k − 1, j − 1));前者表示第 i 天卖出价格,后者表示第 k 天买入价格减去第1次交易利润
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
if(len <= 1) return 0; // 特殊情况处理
if(len == 2 && prices[0] >= prices[1]) return 0; // 特殊情况处理
else if(len == 2 && prices[0] < prices[1])
return prices[1] - prices[0]; // 特殊情况处理
vector<vector<int>> dp(len, vector<int>(3, 0));
for(int j = 1; j <= 2 ; j++){
int minValue = prices[0];
for (int i = 1; i < len; i++){
minValue = min(minValue , prices[i] - dp[i - 1][j - 1]);
dp[i][j] = max(dp[i - 1][j], prices[i] - minValue);
}
}
return dp[len - 1][2];
}
};
LeetCode188. 买卖股票的最佳时机 IV
此题与上一题的不同点在于可以进行k次交易,但解题思路是一样的,只不过当k >= len/2时,可退化为无限次交易的问题。
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int len = prices.size();
if(len <= 1) return 0; // 特殊情况处理
if (k >= len/2){ //当k >= len/2时,可退化为无限次交易的问题
int res = 0;
for (int i = 1; i < len; i ++){
res += max(0, prices[i] - prices[i - 1]);
}
return res;
}
vector<vector<int>> dp(len, vector<int>(k + 1, 0));
for(int j = 1; j <= k ; j++){
int minValue = prices[0];
for (int i = 1; i < len; i++){
minValue = min(minValue , prices[i] - dp[i][j - 1]);
dp[i][j] = max(dp[i - 1][j], prices[i] - minValue);
}
}
return dp[len - 1][k];
}
};
LeetCode312. 戳气球
有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。求所能获得硬币的最大数量。
说明:
你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
示例:
输入: [3,1,5,8]
输出: 167
解释: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
状态表示: f[i][j] 表示戳破 (i, j) 之间所有气球的集合,属性(最大值)这题的状态表示很巧妙,用到了开区间
状态计算:设 i 和 j 之间最后一个被戳破的气球为 k,那此时 (i, k) 和 (k, j) 之间的气球已被戳破,最后 (i, j) 之间只剩下气球 k ,相邻的就是气球 i 和 j,当j − i >= 2时,f[i][j] = f[i][k] + f[k][j] + p[i]∗p[k]∗p[j]
class Solution {
public:
int maxCoins(vector<int>& nums) {
int length = nums.size();
vector<int> tmp(length + 2, 1);
vector<vector<int>> dp(length + 2, vector<int>(length + 2, 0));
for (int i = 1; i <= length; i++)
tmp[i] = nums[i - 1]; //[3,1,5,8]变成[1,3,1,5,8,1]
for (int len = 1; len <= length + 2; len ++) { //len的范围[3,1,5,8,1]
for (int i = 0; i + len - 1 <= length + 1; i++) { //i为左边界范围[1,3,1,5,8]
int j = i + len - 1; //j为右边界
if (len == 1 || len == 2) dp[i][j] = 0; //意味着右边界至少比左边界大2
for (int k = i + 1; k <= j - 1; k++) {
//k为最后戳破的气球,此时 (i,k) 和 (k,j)之间的气球已被戳破
//最后 (i,j) 之间只剩下气球 k
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + tmp[i] * tmp[k] * tmp[j]);
}
}
}
return dp[0][length + 1];
}
};