首页 > 代码库 > Best Time to Buy and Sell Stock III

Best Time to Buy and Sell Stock III

https://oj.leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/

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).

解题思路:

首先想到,按照Best Time to Buy and Sell Stock问题的解法,找到一个最大的区间,然后再找第二大的区间,这个第二大区间必然不能和第一大区间重合,于是便多了一个变量buyDay来记录,除此之外还多了好几个变量。但是,这个方法是错误的。为什么?因为对于可以买卖两次的问题,Best Time to Buy and Sell Stock问题的最大区间,在这里,有时候还不如分开来核算。想想,例如,6,1,3,2,4,7.问题一的最大区间为1-7=6.如果分开两次,1-3+2-7=7.所以以下代码是错误的,而且也不太容易理解。

public class Solution {    public int maxProfit(int[] prices) {        int maxProfit = 0;        int secondMaxProfit = 0;        int profit = 0;        int thisTimeMaxProfit = 0;        int buyDay = 0;        int maxBuyDay = 0;        int buyValue;                if(prices.length == 0){            return 0;        }else {            buyValue = prices[0];            buyDay = 0;        }                for(int i = 0; i < prices.length; i ++){            profit = prices[i] - buyValue;                        if(profit > maxProfit){                maxProfit = profit;                maxBuyDay = buyDay;            }            //更新一个同一个买天的最大值,即local,不是global的最大利润            if(profit > thisTimeMaxProfit){                thisTimeMaxProfit = profit;            }                        //出现新的最低买价,既要看看,这个local区间内的最大利润,即thisTimeMaxProfit,是不是可以作为第二大利润            //所以还要判断买天是不是和最大利润为一天,防止重复            if(prices[i] < buyValue){                if(thisTimeMaxProfit > secondMaxProfit && thisTimeMaxProfit <= maxProfit && buyDay != maxBuyDay){                    secondMaxProfit = thisTimeMaxProfit;                }                buyValue = prices[i];                buyDay = i;                profit = 0;                thisTimeMaxProfit = 0;            }            //在最后一天,如果价格一直上扬,碰不到一个新买点,也要更新第二大利润            if(i == prices.length - 1 && buyDay != maxBuyDay){                if(thisTimeMaxProfit > secondMaxProfit && thisTimeMaxProfit <= maxProfit){                    secondMaxProfit = thisTimeMaxProfit;                }            }        }        return maxProfit + secondMaxProfit;    }}

于是出现了下面的解法。因为两次交易必然不会重合的,肯定被第k天分割在左右。所以遍历整个区间,以每天作为分割,求出左右两个区间的最大利润,再将这一对对利润相加,求出总利润中最大的。但是,这个代码的时间复杂度要O(n^2),超时。

public class Solution {    public int maxProfit(int[] prices) {        int maxProfit = 0;        int profit = 0;        int buyValue;        int leftMaxProfit = 0;        int rightMaxProfit = 0;                if(prices.length == 0){            return 0;        }else {            buyValue = prices[0];        }                for(int i = 0; i < prices.length; i ++){            profit = prices[i] - buyValue;                        if(profit > maxProfit){                maxProfit = profit;            }                        if(prices[i] < buyValue){                buyValue = prices[i];                profit = 0;            }        }                for(int i = 0; i < prices.length; i ++){            buyValue = prices[0];            for(int j = 0; j < i; j++){                profit = prices[j] - buyValue;                            if(profit > leftMaxProfit){                  leftMaxProfit = profit;                }                            if(prices[i] < buyValue){                    buyValue = prices[j];                    profit = 0;                }            }            buyValue = prices[i];            for(int k = i; k < prices.length; k++){                profit = prices[k] - buyValue;                            if(profit > rightMaxProfit){                  rightMaxProfit = profit;                }                            if(prices[i] < buyValue){                    buyValue = prices[k];                    profit = 0;                }            }            if(leftMaxProfit + rightMaxProfit > maxProfit){                maxProfit = leftMaxProfit + rightMaxProfit;            }        }        return maxProfit;    }}

我们只好空间换时间,分开执行两次遍历,但用两个数组,分别记录从左和从右开始的最大利润,最后将两者相加,再次遍历求出总利润最大的。

public class Solution {    public int maxProfit(int[] prices) {        int profit = 0;        int buyValue;        int sellValue;        int[] leftMaxProfit = new int[prices.length];        int[] rightMaxProfit = new int[prices.length];        int[] maxProfit = new int[prices.length];        int maxProfitAll = 0;                if(prices.length == 0){            return 0;        }else {            buyValue = prices[0];        }                for(int i = 1; i < prices.length; i++){            //这里必须注意dp中状态转化方程,不可写为leftMaxProfit[i] = prices[i] - buyValue            //考虑一下,已经过了最大利润区间,利润开始下降,但是该区间内的local最大利润仍然是前面的那个最大值            //所以要在leftMaxProfit[i - 1]和当前值取最大            leftMaxProfit[i] = Math.max(leftMaxProfit[i - 1], prices[i] - buyValue);                        if(prices[i] < buyValue){                buyValue = prices[i];            }        }                if(prices.length == 0){            return 0;        }else {            sellValue = prices[prices.length - 1];        }                for(int i = prices.length - 2; i >= 0; i--){            rightMaxProfit[i] = Math.max(rightMaxProfit[i + 1], sellValue - prices[i]);                        if(prices[i] > sellValue){                sellValue = prices[i];            }        }                for(int i = 0; i < prices.length; i++){            maxProfit[i] = leftMaxProfit[i] + rightMaxProfit[i];        }        for(int i = 0; i < prices.length; i++){            maxProfitAll = Math.max(maxProfitAll, maxProfit[i]);        }                return maxProfitAll;    }}

总结:

从左往右 dp[i+1] = max{dp[i], prices[i+1] - minprices}  ,minprices是区间[0,1,2...,i]内的最低价格

从右往左 dp[i -1] = max{dp[i], maxprices - prices[i-1]}  ,maxprices是区间[i,i+1,...,n-1]内的最高价格。        

Best Time to Buy and Sell Stock III