[LeetCode] 123. Best Time to Buy and Sell Stock III Java
题目:
Say you have an array for which the ith 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 (ie, you must sell the stock before you buy again).
题意及分析:给出一个数组,代表一支股票在某天的价格,求能得到的最大利润。最多可以进行两次交易,同时不能同时持有两支股票。
方法一:因为不能同时持有两支股票,所以肯定是买入卖出一次交易,然后在买入卖出一次交易。这样可以把数组分为两部分,分别求前面的最大利润和后面部分的最大利润,加起来就是当前一次的最大利润,这样有n种可能,最后遍历得到最大利润,因为开始用的是双重循环,所以时间复杂度为0(n^2),提交超时。这里可以使用两次扫描避免双重循环,这样时间复杂度会降低到o(n)
代码:
public class Solution { public int maxProfit(int[] prices) { if(prices ==null || prices.length==0 || prices.length ==1) return 0; int[] leftProfit = new int[prices.length]; int minPrice = prices[0]; int maxProfit = 0; for(int i=1;i<prices.length;i++){ //从前向后找,找到到i点的能得到的最大利润,最大利润为当前价格减去前面的最小价格和前一个最大利润之间的较大值 maxProfit = Math.max(maxProfit,prices[i]-minPrice); minPrice = Math.min(minPrice,prices[i]); leftProfit[i] = maxProfit; } int[] rightProfit = new int[prices.length]; int maxPrice = prices[prices.length-1]; maxProfit = 0; for(int i=prices.length-2;i>=0;i--){ //从后向前找,找到到i点的能得到的最大利润,最大利润为当前最大价格减去当前价格和前后一个最大利润之间的较大值 maxProfit = Math.max(maxProfit,maxPrice-prices[i]); maxPrice = Math.max(maxPrice,prices[i]); rightProfit[i] = maxProfit; } maxProfit = 0; for(int i=0;i<prices.length;i++){ maxProfit = Math.max(maxProfit,leftProfit[i]+rightProfit[i]); } return maxProfit; } }
方法二:
第二种解法的核心是假设手上最开始只有0元钱,那么如果买入股票的价格为price,手上的钱需要减去这个price,如果卖出股票的价格为price,手上的钱需要加上这个price。
它定义了4个状态:
Buy1[i]表示前i天做第一笔交易买入股票后剩下的最多的钱;
Sell1[i]表示前i天做第一笔交易卖出股票后剩下的最多的钱;
Buy2[i]表示前i天做第二笔交易买入股票后剩下的最多的钱;
Sell2[i]表示前i天做第二笔交易卖出股票后剩下的最多的钱;
那么Sell2[i]=max{Sell2[i-1],Buy2[i-1]+prices[i]}
Buy2[i]=max{Buy2[i-1],Sell[i-1]-prices[i]}
Sell1[i]=max{Sell[i-1],Buy1[i-1]+prices[i]}
Buy1[i]=max{Buy[i-1],-prices[i]}
可以发现上面四个状态都是只与前一个状态有关,所以可以不使用数组而是使用变量来存储即可。
代码:
class Solution { public int maxProfit(int[] prices) { if (prices == null || prices.length < 2) return 0; // 四个变量分别表示经过当前操作以后的profit int firstBuy = Integer.MIN_VALUE, firstSell = 0; int secondBuy = Integer.MIN_VALUE, secondSell = 0; for (int curPrice : prices) { firstBuy = Math.max(firstBuy, -curPrice); firstSell = Math.max(firstSell, curPrice + firstBuy); secondBuy = Math.max(secondBuy, firstSell - curPrice); secondSell = Math.max(secondSell, secondBuy + curPrice); } return secondSell; } }
方法三:引用自:http://blog.csdn.net/linhuanmars/article/details/23236995
这里我们先解释最多可以进行k次交易的算法,然后最多进行两次我们只需要把k取成2即可。我们还是使用“局部最优和全局最优解法”。我们维护两种量,一个是当前到达第i天可以最多进行j次交易,最好的利润是多少(global[i][j]),另一个是当前到达第i天,最多可进行j次交易,并且最后一次交易在当天卖出的最好的利润是多少(local[i][j])。下面我们来看递推式,全局的比较简单,
也就是去当前局部最好的,和过往全局最好的中大的那个(因为最后一次交易如果包含当前天一定在局部最好的里面,否则一定在过往全局最优的里面)。对于局部变量的维护,递推式是
也就是看两个量,第一个是全局到i-1天进行j-1次交易,然后加上今天的交易,如果今天是赚钱的话(也就是前面只要j-1次交易,最后一次交易取当前天),第二个量则是取local第i-1天j次交易,然后加上今天的差值(这里因为local[i-1][j]比如包含第i-1天卖出的交易,所以现在变成第i天卖出,并不会增加交易次数,而且这里无论diff是不是大于0都一定要加上,因为否则就不满足local[i][j]必须在最后一天卖出的条件了)。
上面的算法中对于天数需要一次扫描,而每次要对交易次数进行递推式求解,所以时间复杂度是O(n*k),如果是最多进行两次交易,那么复杂度还是O(n)。空间上只需要维护当天数据皆可以,所以是O(k),当k=2,则是O(1)。代码如下:
public int maxProfit(int[] prices) { if(prices==null || prices.length==0) return 0; int[] local = new int[3]; int[] global = new int[3]; for(int i=0;i<prices.length-1;i++) { int diff = prices[i+1]-prices[i]; 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]; }