参考的leetcode#123。但是感觉leetcode的O(1)的解法思路有点反人类,就稍微转换了一下。
先看(四)的解法再看三(因为四不用空间优化, 好理解)。

举例
price: 3  16  6  14  10  19

大致可以理解为把#买卖股票的最好时机(一)#走三遍。

第一遍
和#买卖股票的最好时机(一)#一模一样,记录当前最低价minP。
C1就是1次买卖结束后你手上最多可能有的现金
C1[i] = max(P[j]-minP[j]), for all j<=i
price: 3  16  6  14  10  19
minP:  3  3   3  3   3   3
c1:    0  13  13 13  13  16

第二遍
C2[i]理解为你选择在<=i天第二次买入股票后手上能有的最多的现金
第一次的利润C1,减去第二次买入的股价,取最优(i.e. max):
C2[i] = max(C1[j]-P[j]), for all j<=i
price: 3  16  6  14  10  19
c1:    0  13  13 13  13  16
c2:   -3  -3  7  7   7   7
这个例子里选择在股价为6时第二次买出最优 13-6=7
Ok, 现在你手上有c2的现金和1股。

第三遍
C3[i]就是你最后再把那1股卖掉后你手上能有的最多的现金,也就是这题的解
就是第二次买完能剩的现金C2,加上第二次卖出的股价, 取最优(i.e. max):
C3[i] = max(C2[j] + P[j]), for all j<=i
price: 3  16  6  14  10  19
c1:    0  13  13 13  13  16
c2:   -3  -3  7  7   7   7
c3:    0  13  13 21  21  26
还原一下操作,就是3块买,16卖,6买,19卖。总营收26

这个缕清了之后就是优化记忆。
对于每个i, DP只需要记录之前的C1,C2,C3的max,(当然还有minP).

时间O(n)
空间O(1)
import java.util.*;

public class Solution {
    public int maxProfit (int[] prices) {
      if (prices.length <= 1) return 0;
      
      // 定义 
      //   C1[i]: max profit at day i, with at most 1 buy/sell on or before day i
      //     = Max( Prices[i] - Min{ Prices[j], for all j <= i } )
      //   C2[i]: max cash after buying again on or before day i
      //     = Max{ C1[j] - Price[j], for all j <= i }
      //   C3[i]: max cash after selling again on or before day i
      //     = Max{ C2[j] + Price[j], for all j <= i }
      
      int minP1 = prices[0]; // 目前已知的最低价
      int c1 = 0;
      int c2 = c1-prices[0];
      int c3 = 0; // c2 + price[0] = c1 + price[0] - price[0] = 0
      
      for (int i = 1; i < prices.length; i++) {
        minP1 = Math.min(minP1, prices[i]);
        c1 = Math.max(c1, prices[i] - minP1);
        c2 = Math.max(c2, c1 - prices[i]);
        c3 = Math.max(c3, c2 + prices[i]);
      }
      
      return Math.max(0, c3);
    }
}