# Best Time to Buy and Sell Stock III

## Question

<https://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.
>
> Example
>
> Given an example \[4,4,6,1,1,4,2,5], return 6.

## Analysis

**一种算法比较容易想到：**\
找寻一个点j，将原来的prices\[0..n-1]分割为prices\[0..j]和prices\[j..n-1]，分别求两段的最大profit。\
内层循环与Best Time to Buy and Sell Stock相同，为O(n), 外层循环为O(n)，记录不同的j两段maxProfit的值需要O(n)空间，因此总体时间复杂度为O(n^2)，空间复杂度为O(n)。

**改进（参考pickless的讲解）:**\
对于点j+1，求prices\[0..j+1]的最大profit时，很多工作是重复的，在求prices\[0..j]的maxProfit中已经计算过了。\
类似于Best Time to Buy and Sell Stock，可以在O(1)的时间从prices\[0..j]推出prices\[0..j+1]的最大profit。\
但是如何从prices\[j..n-1]推出prices\[j+1..n-1]？反过来思考，我们可以用O(1)的时间由prices\[j+1..n-1]推出prices\[j..n-1]。\
最终算法：\
数组l\[i]记录了prices\[0..i]的最大profit，\
数组r\[i]记录了prices\[i..n]的最大profit。\
已知l\[i]，求l\[i+1]是简单的，同样已知r\[i]，求r\[i-1]也很容易。\
最后，我们再用O(n)的时间找出最大的l\[i]+r\[i]，即为题目所求。\
这样时间复杂度O(n), 空间复杂度O(n)

参考 <http://blog.csdn.net/fightforyourdream/article/details/14503469>

**状态机State Machine：**

<https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/discuss/149383/Easy-DP-solution-using-state-machine-O(n)-time-complexity-O(1)-space-complexity>

这种想法很神奇：

```
// Assume we are in state S
// If we buy, we are spending money but we can also choose to do nothing
// Doing nothing means going from S->S
// Buying means going from some state X->S, losing some money in the process
S = max(S, X-prices[i])

// Similarly, for selling a stock
S = max(S, X+prices[i])
```

Java Implementation

```java
public class Solution {
    int maxProfit(int[] prices) {
    if(prices == null || prices.length == 0) return 0;
    int s1=-prices[0], s2=Integer.MIN_VALUE,s3=Integer.MIN_VALUE,s4=Integer.MIN_VALUE;

    for(int i=1;i<prices.length;++i) {            
        s1 = Math.max(s1, -prices[i]);
        s2 = Math.max(s2, s1+prices[i]);
        s3 = Math.max(s3, s2-prices[i]);
        s4 = Math.max(s4, s3+prices[i]);
    }
    return Math.max(0,s4);
    }
}
```

## Solution

A Intuitive Implementation Using Divide And Conquer

```java
import java.util.*;

public class Solution {
    /**
     * @param prices: Given an integer array
     * @return: Maximum profit
     */
    public int maxProfit(int[] prices) {
        // find maxProfit for {0, j}, find maxProfit for {j + 1, n - 1}
        // find max for {max{0, j}, max{j + 1, n - 1}}

        if (prices == null || prices.length == 0) {
            return 0;
        }

        int maximumProfit = 0;
        int n = prices.length;

        ArrayList<Profit> preMaxProfit = new ArrayList<Profit>(n);
        ArrayList<Profit> postMaxProfit = new ArrayList<Profit>(n);
        for (int i = 0; i < n; i++) {
            preMaxProfit.add(maxProfitHelper(prices, 0, i));
            postMaxProfit.add(maxProfitHelper(prices, i + 1, n - 1));
        }
        for (int i = 0; i < n; i++) {
            int profit = preMaxProfit.get(i).maxProfit + postMaxProfit.get(i).maxProfit;
            maximumProfit = Math.max(profit, maximumProfit);
        }
        return maximumProfit;
    }

    private Profit maxProfitHelper(int[] prices, int startIndex, int endIndex) {
        int minPrice = Integer.MAX_VALUE;
        int maxProfit = 0;
        for (int i = startIndex; i <= endIndex; i++) {
            if (prices[i] < minPrice) {
                minPrice = prices[i];
            }
            if (prices[i] - minPrice > maxProfit) {
                maxProfit = prices[i] - minPrice;
            }
        }
        return new Profit(maxProfit, minPrice);
    }

    public static void main(String[] args) {
        int[] prices = new int[]{4,4,6,1,1,4,2,5};
        Solution s = new Solution();
        System.out.println(s.maxProfit(prices));
    }
};

class Profit {
    int maxProfit, minPrice;
    Profit(int maxProfit, int minPrice) {
        this.maxProfit = maxProfit;
        this.minPrice = minPrice;
    }
}
```

Another Implementation with Dynamic Programming

```java
public class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length <= 1) {
            return 0;
        }

        int[] left = new int[prices.length];
        int[] right = new int[prices.length];

        // DP from left to right;
        left[0] = 0;
        int min = prices[0];
        for (int i = 1; i < prices.length; i++) {
            min = Math.min(prices[i], min);
            left[i] = Math.max(left[i - 1], prices[i] - min);
        }

        //DP from right to left;
        right[prices.length - 1] = 0;
        int max = prices[prices.length - 1];
        for (int i = prices.length - 2; i >= 0; i--) {
            max = Math.max(prices[i], max);
            right[i] = Math.max(right[i + 1], max - prices[i]);
        }

        int profit = 0;
        for (int i = 0; i < prices.length; i++){
            profit = Math.max(left[i] + right[i], profit);  
        }

        return profit;
    }
}
```

State Machine

```java
class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0) return 0;
        return maxProfit(prices, 2);
    }
    private int maxProfit(int[] array, int t) {
        int[] stock = new int[t * 2];
        Arrays.fill(stock, Integer.MIN_VALUE);
        stock[0] = -array[0];
        for(int i = 1; i < array.length; ++i) {
            stock[0] = Math.max(stock[0], -array[i]);
            for (int j = 1; j < 2 * t; j += 2) {
                stock[j] = Math.max(stock[j], stock[j - 1] + array[i]);
                if (j + 1 < 2 * t) {
                    stock[j + 1] = Math.max(stock[j + 1], stock[j] - array[i]);
                }
            }
        }
        return Math.max(0, stock[2 * t - 1]);
    }
}
```

## Reference

<http://liangjiabin.com/blog/2015/04/leetcode-best-time-to-buy-and-sell-stock.html>\
<http://www.jiuzhang.com/solutions/best-time-to-buy-and-sell-stock-iii/>
