买卖股票的最佳时机系列问题是一个动态规划问题,可以抽象出一个动态规划的模板来解决此类问题。因为每一天只有三种选择:买入、卖出、什么都不做。限制的状态有:买卖的次数、手续费、冷冻期等。但是对于限制条件只有买卖至多一次和不限制次数这两种情况,可以有更为简单的方法,如下面买卖股票问题的前两题:121、122题。但是如果限制买卖次数为2次、3次、5次…k次这些情况就需要用动态规划来解决了。总结模板的思路看此博客:股票问题系列通解(转载翻译),下面用的解答方法没有用到此模板。
文章目录
一、LeetCode 121.买卖股票的最佳时机
描述
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
思路
题目中规定了是求最多买卖1次的最大利润。比较简单的办法是双指针,设置两个left和right指针,left记录买入的最低价格,那么可以使用right指针遍历每一天,判断right当天的价格是否比left那天的价格更低,如果更低则说明left那天买入不如今天买入,则让left更新为right的值,如果right当天价格大于left那天,说明现在right当天卖出会有利润,则判断利润是否大于之前的利润值,再决定是否更新maxProfit的值。这样遍历到最后一天,便可以得到最大利润值。最终结果:(就1次遍历,咋这么耗时耗内存…)
解答
class Solution {
public:
int maxProfit(vector<int>& prices) {
int maxProfit = 0;//记录最大利润
//双指针,left和right指针,left记录买入的最低价格
for(int l = 0, r = 1;r < prices.size(); ++ r){
if(prices[r] < prices[l]) l = r;
else
maxProfit = max(maxProfit, prices[r] - prices[l]);
}
return maxProfit;
}
};
二、LeetCode 122.买卖股票的最佳时机Ⅱ
描述
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路
这道题变成了不限制买卖的次数时求最大利润,唯一的限制是买入之后必须先卖出才能继续买入。最简单的思路就是贪心算法,只需要考虑今天买入明天卖出能否获利即可,只要能获利则今天买入明天卖出,不考虑后天的事。这样一定会在获利最大的情况下进行最多的买卖次数,因为可能会在当天卖出之后再次重新买入。
解答
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
int sum = 0;//总利润
for(int i=0;i < len-1;++i)//贪心算法
if(prices[i+1]-prices[i] > 0) sum += prices[i+1] - prices[i];
return sum;
}
};
三、LeetCode 123.买卖股票的最佳时机Ⅲ
描述
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路
解答来自评论,是不太标准的动态规划解法。使用firstBuy、firstSell记录第一次买入、卖出后手中剩余的钱;secondBuy和secondSell记录第二次买入、卖出后手中剩余的钱。
解答
class Solution {
public:
int MIN_VALUE = -2147483648;
int maxProfit(vector<int>& prices) {
int firstBuy = MIN_VALUE, firstSell = 0;//第1次买入、卖出后手中剩余的钱
int secondBuy = MIN_VALUE, secondSell = 0;//第2次买入、卖出后手中剩余的钱
for(int i = 0;i != prices.size(); ++ i){
firstBuy = max(firstBuy, -prices[i]);
firstSell = max(firstSell, firstBuy + prices[i]);
secondBuy = max(secondBuy, firstSell - prices[i]);
secondSell = max(secondSell, secondBuy + prices[i]);
}
return secondSell;
}
};
四、LeetCode 188.买卖股票的最佳时机Ⅳ
描述
给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路
在上一题的基础上稍加改动,单独将第一次买入卖出拿出来计算。
解答
class Solution {
public:
int MIN_VALUE = -2147483648;//int最小值
int maxProfit(int k, vector<int>& prices) {
if(k == 0) return 0;//允许交易次数为0次直接返回0
vector<int> buy(k+1, MIN_VALUE);//购买数组,下标记录第几次买入
vector<int> sell(k+1, 0);//卖出数组,下标记录第几次卖出
for(int i = 0;i != prices.size(); ++ i){
buy[1] = max(buy[1], -prices[i]);//第1次买入卖出比较特殊,单独算,k从1开始
sell[1] = max(sell[1], buy[1] + prices[i]);
for(int j = 2;j != k + 1;++ j){
buy[j] = max(buy[j], sell[j-1] - prices[i]);
sell[j] = max(sell[j], buy[j] + prices[i]);
}
}
return sell[k];
}
};
五、LeetCode 309. 最佳买卖股票时机含冷冻期
描述
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
思路
动态规划,每天只能有一种操作,三个数组记录三种状态。
解答
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if(n == 0) return 0;
vector<int> buy(n, 0);//记录到第i天为止,之前所有天的最后操作为买入时的最大收益
vector<int> sell(n, 0);//记录第i天操作为卖出时的最大收益
vector<int> freeze(n, 0);//记录到第i天为止最后操作是冻结时(无操作)的最大收益
buy[0] = -prices[0];
//每天只能有一种操作:买入、卖出、无操作
for(int i = 1;i < n;++ i){
buy[i] = max(freeze[i - 1] - prices[i], buy[i - 1]);//第i天买入或之前就已经买入
sell[i] = buy[i - 1] + prices[i];//第i天卖出
freeze[i] = max(sell[i - 1], freeze[i - 1]);//第i-1天卖出或者第i-1天无操作
}
return max(sell[n - 1], freeze[n - 1]);
}
};
六、LeetCode 714. 买卖股票的最佳时机含手续费
描述
给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
思路
这里注意一点,针对买入时就扣除手续费的情况,下面两句:notHave = max(notHave, have + prices[i] - fee);
have = max(have, notHave - prices[i]);
,有人会有疑问,notHave更新为have + prices[i] - fee的值后,那么have用的不就是notHave更新后的值了吗,不应该用notHave之前的值吗?假设have+price>notHave,移项得notHave-price<have,那么notHave-price-fee更是<have,所以第二步是一定不会更新have的值的;如果have+price<notHave,那么notHave不更新,第二步have还是用的之前的notHave值来更新。对于卖出时扣手续费的情况类似。
解答
买入时就扣除手续费。
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
if (n < 2) return 0;
int notHave = 0;//手上无股票的最大收入
int have = -prices[0] - fee;//手上有股票的最大收入
for(int i = 1;i < n; ++ i){
//第i([0, n-1])天交易结束时,手里无股票:1.要么当天不交易;2.要么之前买了股票今天卖出,减去手续费
notHave = max(notHave, have + prices[i]);
//第i天结束时,手里有股票:1.要么之前已经买了股票,今天不卖;2.或者之前没有买股票,今天买入
have = max(have, notHave - prices[i] - fee);
}
return notHave;//最终手里没股票的最大利润
}
};
或者卖出时扣除手续费。
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
if (n < 2) return 0;
int notHave = 0;//手上无股票的最大收入
int have = -prices[0];//手上有股票的最大收入
for(int i = 1;i < n; ++ i){
//第i([0, n-1])天交易结束时,手里无股票:1.要么当天不交易;2.要么之前买了股票今天卖出,减去手续费
notHave = max(notHave, have + prices[i] - fee);
//第i天结束时,手里有股票:1.要么之前已经买了股票,今天不卖;2.或者之前没有买股票,今天买入
have = max(have, notHave - prices[i]);
}
return notHave;//最终手里没股票的最大利润
}
};