目录
LeetCode121.买卖股票的最佳时机
给定一个数组
prices
,它的第i
个元素prices[i]
表示一支给定股票第i
天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回
0
。
思路:今天讲解股票系列问题。
这里是需要从一个标记价格的天数范围内,先买入,再卖出,求最大利润是多少。
这里介绍两种方法。
1. 贪心算法
按照贪心的想法,那么我只要找到这个范围内的最小值购入,最大值卖出即能收获到最大利润。
但是需要注意,一定要先购入才能卖出,存在顺序问题,不是简单的找最小,找最大,两者相减即是答案,这样是错误的,比如有[7,1,5],没有办法说最大利润是7-1等于6,因为最小值在最大值右边了。
因此需要找到左边的最小值,右边的最大值,两者相减才是最终最大利润!
所以设置一个low记录左边最小值,result记录最终的最大利润,遍历完成后即得最终结果。
int maxProfit(vector<int>& prices) {
int low = INT_MAX;//记录购入的最小值
int result = 0;
for(int i = 0; i < prices.size(); i ++){
low = min(low, prices[i]);//取最小的值购入
result = max(result, prices[i] - low);//取最大值卖出
//这样就能保证这样一入一出后利润最大
}
return result;
}
时间复杂度:O(n)
空间复杂度:O(1)
2. 动态规划
使用动态规划的话,比较好记录前面的情况状态。
这里使用dp[i][0]表示没有持有股票时的最大利润,dp[i][1]表示持有股票的时候的最大利润。
每一天都有这样大小为2的数组记录状态,这样就能在递推中得到最终结果。
当第i天不持有股票的时候,那么它可以保持前面不购入的状态dp[i-1][0],或者是前面一天购入的情况下,在第i天卖出,dp[i-1][1]+prices[i],同样是第i天没有持有股票,所求为最大利润,所以每次都是取两者中的最大值;
同样,当第i天持有股票的时候,那么它可以保持前面购入的状态dp[i-1][1],或者是前面一天不持有股票的情况下,第i天买入,dp[i-1][0]-prices[i],同样是第i天持有股票的情况,两者取最大即可。
最后需要说明的是,最后不持有股票一定是比还持有股票赚得多的。因为一旦持有而没有将其卖出,大概率是亏本的,利润不是最大的。所以最后返回的是dp[prices.size()-1][0]。
int maxProfit(vector<int>& prices) {
//dp[i][0]表示没有持有股票时的最大利润,
//dp[i][1]表示持有股票时的最大利润
vector<vector<int>> dp(prices.size(), vector<int>(2, 0));
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]);//没有持有股票时,可以是保持之前没有持有股票时的状态,也可以是前一个持有股票状态变换到第i天的时候卖出时的状态
dp[i][1] = max(dp[i - 1][1], - prices[i]);//持有股票时,可以是保持前面持有的状态,或者是购入第i件商品
}
return dp[prices.size() - 1][0];//最后卖出的状态一定是比手持股票的利润要大
}
时间复杂度:O(n)
空间复杂度:O(n)
当然,可以对上面动态规划进行空间的优化,因为整个循环都是在维护当前的与前一个的关系,所以可以尝试使用一个2x2的滚动数组进行状态的记录。这样是可以减少空间的消耗。
int maxProfit(vector<int>& prices) {
//dp[i][0]表示没有持有股票时的最大利润,
//dp[i][1]表示持有股票时的最大利润
vector<vector<int>> dp(2, vector<int>(2, 0));
dp[0][0] = 0;
dp[0][1] = - prices[0];
for(int i = 1; i < prices.size(); i ++){
dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] + prices[i]);
dp[i % 2][1] = max(dp[(i - 1) % 2][1], - prices[i]);
}
return dp[(prices.size() - 1) % 2][0];//最后卖出的状态一定是比手持股票的利润要大
}
时间复杂度:O(n)
空间复杂度:O(1)
LeetCode122.买卖股票的最佳时机II
给你一个整数数组
prices
,其中prices[i]
表示某支股票第i
天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
思路:这道题与上面的题的区别就在于每一天都可以进行买入卖出,不再仅仅是只能买入卖出仅一次。
同样这里介绍两种方法。
1. 贪心算法
之前其实在贪心系列的时候做过这道题,因为现在每天都可以选择买入卖出了,那么局部最优就是找到产生的正利润,每次只加正利润,这样最后的整体最优就是所有正利润之后,这也就是最大利润。
int maxProfit(vector<int>& prices) {
int count = 0;//统计总的正利润
for(int i = 1; i < prices.size(); i ++){
//只统计正的利润,最后总和就是最大利润
if(prices[i] > prices[i - 1]) count += prices[i] - prices[i - 1];
}
return count;
}
时间复杂度:O(n)
空间复杂度:O(1)
2. 动态规划
动态规划的整体思路其实和上面的题几乎完全一样,唯一不同的是对于dp[i][1]的递推公式。
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i])
之所以会有加粗的部分,就是因为现在买入卖出的次数不止一次了,也许在进行这次买入股票时,前面已经完成过了买入卖出的操作,积累了一些利润了,所以在这里是需要将其加上的。
这也是与第一题的区别所在。
int maxProfit(vector<int>& prices) {
//dp[i][0]表示没有持有股票时的最大利润
//dp[i][1]表示持有股票时的最大利润
vector<vector<int>> dp(prices.size(), vector<int>(2, 0));
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]);
//这里和上一题的主要区别在于每天都可以买入卖出,买入卖出不再局限于各仅一次
//也就是说可能前面买入卖出已经有了利润dp[i - 1][0],所以需要在这里使用前面的状态
}
return dp[prices.size() - 1][0];
}
时间复杂度:O(n)
空间复杂度:O(n)
当然还是可以进行空间的优化,节省空间的消耗。
int maxProfit(vector<int>& prices) {
//dp[i][0]表示没有持有股票时的最大利润
//dp[i][1]表示持有股票时的最大利润
vector<vector<int>> dp(2, vector<int>(2, 0));
dp[0][0] = 0;
dp[0][1] = - prices[0];
for(int i = 1; i < prices.size(); i ++){
dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] + prices[i]);
dp[i % 2][1] = max(dp[(i - 1) % 2][1], dp[(i - 1) % 2][0] - prices[i]);
}
return dp[(prices.size() - 1) % 2][0];
}
时间复杂度:O(n)
空间复杂度:O(1)
LeetCode123.买卖股票的最佳时机III
给定一个数组,它的第
i
个元素是一支给定的股票在第i
天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:这道题相对于前面两道题稍微就有点难了。
因为它要考虑的状态多了。最多完成两笔交易,或许完成两笔,或许完成一笔,或许什么操作也不做。
于是,我们这里设置了5个状态,0表示无操作,1表示第一次买入股票,2表示第一次卖出股票,3表示第二次买入股票,4表示第二次卖出股票。
这里的买入或者卖出不是指在当时的i时必须要买入或者卖出,也可以保持前一个买入或者卖出的状态,理解了这一点,这道题也就变得简单起来了。
为什么这么说呢?
因为你会发现这些状态的递推公式及其相似,和我们之前做过的两道题的主题逻辑是一样的。
如果想要在第i天购入股票,那么就可以选择保持为前一个已经持有股票的状态,也可以选择在前一个没有持有股票的状态下,买入股票,取两者的最大值即可;
同样,想要在第i天卖出股票,那么就可以选择保持为前一个已经卖出,没有持有股票的状态,也可以选择在前一个持有股票的状态下,卖出股票,取两者的最大值即可。
所以这样看下来其实就简单了。
这里还是需要说明一点,最后一定是找卖出的状态下的利润,那这里有两个,一个是第一次卖出,一个是第二次卖出,该选谁呢?
应该选第二次卖出的状态。因为两次的买入卖出的利润一定是大于等于一次的买入卖出的,这里求的就是最大值,所以不会出现第二次买入卖出还会比第一次低的。
有人说,万一第一次卖出就是最大值了呢?那其实可以这样理解,在第一次卖出就是利润最大值的当天,买入卖出,相当于其实还是第一次卖出的最大值,所以第二次卖出后的最大利润是包含了第一次卖出的最大利润的。因此这里选择返回第二次卖出的结果。
int maxProfit(vector<int>& prices) {
//这里设置5个状态
//0表示没有任何操作,1表示第1次买入,2表示第1次卖出,3表示第2次买入,4表示第2次卖出
vector<vector<int>> dp(prices.size(), vector<int>(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][0] = dp[i - 1][0];
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 dp[prices.size() - 1][4];//两次买入卖出后,该价值一定是最大的
}
时间复杂度:O(n)
空间复杂度:O(n)
这里其实可以不用设置状态为0的数组,因为不操作本身就是0了,如果操作后有正利润,自然就会将0覆盖,所以可以不用设置状态为0的数组。
int maxProfit(vector<int>& prices) {
//这里设置4个状态
//0表示第1次买入,1表示第1次卖出,2表示第2次买入,3表示第2次卖出
vector<vector<int>> dp(prices.size(), vector<int>(4, 0));
dp[0][0] = - prices[0];
dp[0][1] = 0;
dp[0][2] = - prices[0];
dp[0][3] = 0;//初始化,这里第0天的初始化可以看作当天买入卖出,相当于没赚也没亏
for(int i = 1; i < prices.size(); i ++){
dp[i][0] = max(dp[i - 1][0], - prices[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]);
}
return dp[prices.size() - 1][3];//两次买入卖出后,该价值一定是最大的
}
这里还可以使用一个一维滚动数组来优化空间消耗,减少空间复杂度。
int maxProfit(vector<int>& prices) {
//这里设置4个状态
//0表示第1次买入,1表示第1次卖出,2表示第2次买入,3表示第2次卖出
vector<int> dp(4, 0);
dp[0] = - prices[0];
dp[1] = 0;
dp[2] = - prices[0];
dp[3] = 0;//初始化,这里第0天的初始化可以看作当天买入卖出,相当于没赚也没亏
for(int i = 1; i < prices.size(); i ++){
dp[0] = max(dp[0], - prices[i]);
dp[1] = max(dp[1], dp[0] + prices[i]);
dp[2] = max(dp[2], dp[1] - prices[i]);
dp[3] = max(dp[3], dp[2] + prices[i]);
}
return dp[3];//两次买入卖出后,该价值一定是最大的
}
时间复杂度:O(n)
空间复杂度:O(1)
感谢你的阅读,希望我的文章能够给你帮助,如果有帮助,麻烦点赞加收藏,或者点点关注,非常感谢。
如果有什么问题欢迎评论区讨论!