原题链接:https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/
Buy and Sell Stock专题相关题目
121. Best Time to Buy and Sell Stock
122. Best Time to Buy and Sell Stock II
123. Best Time to Buy and Sell Stock III
188. Best Time to Buy and Sell Stock IV
309. Best Time to Buy and Sell Stock with Cooldown
714. Best Time to Buy and Sell Stock with Transaction Fee
1. 题目简介
Say you have an array for which the i th element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete at most two transactions.
Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).
给出一列数组,第i个元素代表着第i天某支股票的价格。最多可以交易2次,也就是最多买2次,卖2次,而且必须先买后卖。
Example 1:
Input: [3,3,5,0,0,3,1,4]
Output: 6
Explanation:
Buy on day 4 (price = 0) and sell on day 6 (price = 3),
profit = 3-0 = 3.
Then buy on day 7 (price = 1) and sell on day 8 (price = 4),
profit = 4-1 = 3.
Example 2:
Input: [1,2,3,4,5]
Output: 4
Explanation:
Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4.
Note that you cannot buy on day 1, buy on day 2 and sell them later,
as you are engaging multiple transactions at the same time.
You must sell before buying again.
Example 3:
Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.
2. 解题思路
动态规划法
注:这部分内容参考了 https://blog.csdn.net/linhuanmars/article/details/23236995 的解题思路。
步骤1: 设计数组
最多可以进行2次交易,我们把这个次数抽象化,变成最多可以进行k次交易,最后的答案只需要把k取2即可。
对于最多进行k次交易的情况,可以采用“局部最优+全局最优”的方法,需要设计2个二维数组,一个是全局最优,一个是局部最优。
全局最优 :global [ i ][ j ],存放当前到达第 i 天时,最多进行 j 次交易,所获得的最大利润,并且第 j 次交易不是在当天(第 i 天)卖出的。
局部最优 :local [ i ][ j ],存放当前到达第 i 天时,最多进行 j 次交易,规定其中最后一次交易必须是当天(第 i 天)卖出的,所获得的最大利润。
步骤2:寻找状态转移方程
1 . 全局最优的状态转移方程:
global[i][j] = max( local[i][j] , global[i-1][j] )
在 j 次交易下,前一天的全局最优 和 今天的局部最优,哪个更大,哪个就是 今天的全局最优。
2 . 局部最优的状态转移方程:
local[i][j] = max(global[i-1][j-1]+max(diff,0) , local[i-1][j]+diff)
其中diff = prices[i]-prices[i-1]
global[ i-1 ][ j-1 ]+ max(diff,0) 的意思是,是在前面 j-1 次交易的基础上,最后一次交易(第 j 次)在倒数第二天(第 i-1 天)买进,在最后一天(第 i 天)卖出 所获得的利润。
这里特别需要强调的是,global[ i-1 ][ j-1 ]意味着第i-1天没有交易,因此不会出现在第 i-1 天同时买卖的矛盾。
local[ i-1 ][ j ] + diff 的意思是,在前面 j 次交易的基础上,把最后一次交易从第 i-1 天卖出改成第 i 天卖出。由于local[ i-1 ][ j ]意味着最后一次交易(第 j 次交易)一定在 i-1 天卖出,如果改成在第 i 天卖出,会把第 i 天卖的合并到之前 i-1 卖出的那次交易中,因此并不会增加交易次数,所以是 local[ i-1 ][ j ] + diff
步骤3:举例说明
以[3,3,5,0,0,3,1,4]为例。按照上述方法进行递推,结束后的local数组和global数组为:
实现代码
class Solution {
public int maxProfit(int[] prices) {
int length = prices.length;
if(prices==null || length==0)
return 0;
int[][] local = new int[length][3];
int[][] global = new int[length][3];
for(int i = 1; i<length ; i++)
{
int diff = prices[i]-prices[i-1];
for(int j=1;j<=2;j++)
{
local[i][j] = Math.max(global[i-1][j-1]+(diff>0?diff:0) , local[i-1][j]+diff);
global[i][j] = Math.max( local[i][j] , global[i-1][j] );
}
}
return global[length-1][2];
}
}
步骤4:进一步优化
二维数组转化为一维数组,节省空间。
从下图可以看出,更新global数组时,我们每次想要更新global[ i ][ j ](下图黄色的部分),只会用到 local[i][j] 和 global[i-1][j](下图蓝色的部分)
更新local数组时,我们每次想要更新local[ i ][ j ](下图黄色的部分),只会用到 global[i-1][j-1] 和 local[i-1][j](下图蓝色的部分)
计算prices[ i ]时,上一行的数值在计算prices[ i-1 ]时已经算好了。那我们就直接用起来,用完不用再保存了。
所以,我们就能把二维数组压缩为一维数组,只需要每次 从后往前 更新global 和 local 的值。状态转移方程也就成了:
local[j] = Max(global[j-1]+ Max(diff,0) , local[j]+diff);
global[j] = Max( local[j] , global[j] );
实现代码
class Solution {
public int maxProfit(int[] prices) {
int length = prices.length;
if(prices==null || length==0)
return 0;
int[] local = new int[3];
int[] global = new int[3];
for(int i = 1; i<length ; i++)
{
int diff = prices[i]-prices[i-1];
for(int j=2;j>=1;j--)
{
local[j] = Math.max(global[j-1]+(diff>0?diff:0) , local[j]+diff);
global[j] = Math.max( local[j] , global[j] );
}
}
return global[2];
}
}