LeetCode121&122.买卖股票的最佳时期
前言
今天记录一下 「LeetCode121.买卖股票的最佳时期」和「LeetCode122.买卖股票的最佳时期Ⅱ」 两个问题的解法。先理解如何利用贪心算法解决第二题,再去想第一题,立马就能类比得到解决问题的思路,将其转化为最大连续子列和问题,并利用在线处理算法得到结果。
问题描述1
这是 「LeetCode122.买卖股票的最佳时期Ⅱ」
给定一个数组 prices ,其中 prices[i]表示一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。
你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例1
|| 输入:[7,1,5,3,6,4] || 输出:7 || 解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例2
|| 输入:[7,6,4,3,1] || 输出:0 || 解释:在这种情况下,没有交易完成,所以最大利润为0。
贪心算法解决
如下图所示的一个股票曲线图,可以直观地看到,只需要找到 「股票开始上涨的最小值」和「股票上涨到的最大值」 ,在最小值点买入股票并在最大值点卖出股票,计算这一段之间的差,就是折线递增区间的增量,也就是这段时间内股票的最大利润。最后只需要把所有上涨时间段内的利润累加起来就是要求的结果。 贪心算法就是指,在对问题求解的时候,总是做出当前来看是最好的选择,某种意义上就是局部最优解。
在这道题目中,只要是在上升的,我们就计算上升的增量,然后对所有增量进行累加,并不需要找到这段完整增量区间的最小值和最大值再来求差值。
我们结合图片来理解一下,当连续几天股价都在增长的情况下,可以看成在上升的第二个点,我先卖出再买入。连续下降的时候我就不买入,这一天我手中是没有股票的。
也可以结合数学公式来理解,在一段上升区间 a<b<c,则这段区间的增量是 (c-a),同样可以这样计算 (b-a)+(c-b),到这一步应该一切都明明白白了。
我们只要把 prices 数组遍历一遍,并且在遍历的过程中用后一项减前一项,如果是正数则累加,负数则不加,得到 4+3=7。 这个时候代码就呼之欲出了:
class Solution{
public:
int maxProfit(vector<int>&prices){
int total = 0;
for(int i=0;i<prices.size()-1;i++){
total += max(prices[i+1]-prices[i],0);
}
return total;
}
};
问题描述2
这是 「LeetCode121.买卖股票的最佳时期」
给定一个数组 prices ,它的第 i 个元素 prices[i]表示一支给定股票第 i 天的价格。
你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。
设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。
如果你不能获取任何利润,返回 0 。
示例1
|| 输入:[7,1,5,3,6,4] || 输出:7 || 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例2
|| 输入:[7,6,4,3,1] || 输出:0 || 解释:在这种情况下,没有交易完成,所以最大利润为0。
转化为最大连续子列和问题
这道题是让求完成一笔交易所获得的最大利润,并且在有交易完成的情况下,只能买入一次和卖出一次,有了上一题的经验,很容易将这个问题转化为「最大连续子列和」 的问题。
如下图展示的股票曲线图,上一题是避免了所有股票下跌的情况,只要上升我的利润就增加。而这道题我只能在某个区间的起点买入,在终点卖出,这个区间中股价有涨有跌,但总体上来说我是获利的,否则我一开始就不会买入。 通过观察可以发现,在这个区间的最小值点买入,在最大值点卖出,中间即便股价下跌也不会跌出最小值,即便有涨也不会超出最大值。
那么我们要怎么找出使得利润最大的这个区间呢?回顾上一题,我们用数组的后一个值减前一个值,并且将差为正的数累加,得到利润。而这一题,得到所有的差值之后,要使某个子区间内的差都累加起来(无论正负)的值达到最大,那么求 [7,1,5,3,6,4] 中最大利润的问题就转化为了求 [-6,4,-2,3,-2] 中最大连续子列和的问题。
在线处理算法解决
如果我们使用for循环把所有子列和情况都算出来再求出最大值的话,那显然时间复杂度太大了。所以这里采用「在线处理算法」,即使数据一个一个读入,返回的最大连续子列和也是当前正确的,算法时间复杂度为O(n)。
对于待求数组 [-6,4,-2,3,-2] ,我们将每一项向右累加,发现更大的和则更新当前结果,如果当前子列和为负,它一定会使后面的数字变得更小,干脆抛弃,将当前子列和置为0,最后得到的结果即为最大连续子列和。
只要一次遍历,将数组 prices 的后一项减去前一项,得到的值进入「在线处理算法」,相当于数据一个一个读入,得到最大连续子列和,即最大利润。思路有了,代码同样是呼之欲出:
class Solution {
public:
int maxProfit(vector<int>& prices) {
int len = prices.size();
int maxSum = 0;
int thisSum = 0;
for(int i=1;i<len;++i){
thisSum += prices[i]-prices[i-1];
if(thisSum>maxSum)
maxSum = thisSum;
else if(thisSum<0)
thisSum = 0;
}
return maxSum;
}
};
微信公众号文章
LeetCode121&122.买卖股票的最佳时期
|