import java.util.*;

/**
 * NC167 买卖股票的最好时机(四)
 * @author d3y1
 */
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param prices int整型一维数组
     * @param k int整型
     * @return int整型
     */
    public int maxProfit (int[] prices, int k) {
        // return solution1(prices, k);
        return solution2(prices, k);
        // return solution3(prices, k);
        // return solution4(prices, k);
        // return solution5(prices, k);
    }

    /**
     * 动态规划: 状态机
     *
     * dp[i][1]表示股票前i天价格第一次交易买入后所得收益
     * dp[i][2]表示股票前i天价格第一次交易卖出后所得收益
     * dp[i][3]表示股票前i天价格第二次交易买入后所得收益
     * dp[i][4]表示股票前i天价格第二次交易卖出后所得收益
     * ...
     * dp[i][2k-1]表示股票前i天价格第k次交易卖出后所得收益
     * dp[i][2k]  表示股票前i天价格第k次交易卖出后所得收益
     *
     * 2k种状态:
     * 第一次交易买入: 要么是之前买的, 要么是今天买的
     * 第一次交易卖出: 要么是之前卖的, 要么是今天卖的
     * 第二次交易买入: 要么是之前买的, 要么是今天买的
     * 第二次交易卖出: 要么是之前卖的, 要么是今天卖的
     * ...
     * 第k次交易买入: 要么是之前买的, 要么是今天买的
     * 第k次交易卖出: 要么是之前卖的, 要么是今天卖的
     *
     * 关系式:
     *            { Math.max(dp[i-1][j], dp[i-1][j-1]-prices[i-1])  , j%2 == 1
     * dp[i][j] = {
     *            { Math.max(dp[i-1][j], dp[i-1][j-1]+prices[i-1])  , j%2 == 0
     *
     * @param prices
     * @param k
     * @return
     */
    private int solution1(int[] prices, int k){
        int n = prices.length;
        k = Math.min(k, n/2);

        int[][] dp = new int[n+1][2*k+1];

        // init
        for(int j=1; j<2*k+1; j++){
            if(j%2 == 1){
                dp[1][j] = -prices[0];
            }else{
                dp[1][j] = 0;
            }
        }

        for(int i=2; i<=n; i++){
            for(int j=1; j<2*k+1; j++){
                if(j%2 == 1){
                    dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-1]-prices[i-1]);
                }else{
                    dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-1]+prices[i-1]);
                }
            }
        }

        return dp[n][2*k];
    }

    /**
     * 动态规划: 三维数组
     *
     * dp[i][j][0]表示前i天股票价格交易j次(一买一卖)后手上未持股票时的最大收益
     * dp[i][j][1]表示前i天股票价格交易j次(一买一卖)后手上持有股票时的最大收益
     *
     * dp[i][j][0] = Math.max(dp[i-1][j][0], dp[i-1][j][1]+prices[i])    , 1<=i<n && 1<=j<=k
     * dp[i][j][1] = Math.max(dp[i-1][j][1], dp[i-1][j-1][0]-prices[i])  , 1<=i<n && 1<=j<=k
     *
     * @param prices
     * @param k
     * @return
     */
    private int solution2(int[] prices, int k){
        int n = prices.length;
        k = Math.min(k, n/2);

        int[][][] dp = new int[n][k+1][2];

        // init
        for(int j=1; j<=k; j++){
            dp[0][j][0] = 0;
            dp[0][j][1] = -prices[0];
        }

        for(int i=1; i<n; i++){
            for(int j=1; j<=k; j++){
                // dp[i-1][j][1]+prices[i] -> 卖(次数不变, j -> j)
                dp[i][j][0] = Math.max(dp[i-1][j][0], dp[i-1][j][1]+prices[i]);
                // dp[i-1][j-1][0]-prices[i] -> 买(次数加1, j-1 -> j)
                dp[i][j][1] = Math.max(dp[i-1][j][1], dp[i-1][j-1][0]-prices[i]);
            }
        }

        return dp[n-1][k][0];
    }

    /**
     * 动态规划: 二维数组
     *
     * sell[i][j]表示前i天股票价格交易j次(一买一卖)后手上未持股票时的最大收益
     * buy[i][j]表示前i天股票价格交易j次(一买一卖)后手上持有股票时的最大收益
     *
     * sell[i][j] = Math.max(sell[i-1][j], buy[i-1][j]+prices[i])   , 1<=i<n && 1<=j<=k
     * buy[i][j] = Math.max(buy[i-1][j], sell[i-1][j-1]-prices[i])  , 1<=i<n && 1<=j<=k
     *
     * @param prices
     * @param k
     * @return
     */
    private int solution3(int[] prices, int k){
        int n = prices.length;
        k = Math.min(k, n/2);

        int[][] sell = new int[n][k+1];
        int[][] buy = new int[n][k+1];

        // init
        for(int j=1; j<=k; j++){
            sell[0][j] = 0;
            buy[0][j] = -prices[0];
        }

        for(int i=1; i<n; i++){
            for(int j=1; j<=k; j++){
                // buy[i-1][j]+prices[i] -> 卖(次数不变, j -> j)
                sell[i][j] = Math.max(sell[i-1][j], buy[i-1][j]+prices[i]);
                // sell[i-1][j-1]-prices[i] -> 买(次数加1, j-1 -> j)
                buy[i][j] = Math.max(buy[i-1][j], sell[i-1][j-1]-prices[i]);
            }
        }

        return sell[n-1][k];
    }

    /**
     * 动态规划: 一维数组(空间压缩)
     *
     * sell[j]表示交易j次(一买一卖)后手上未持股票时的最大收益
     * buy[j]表示交易j次(一买一卖)后手上持有股票时的最大收益
     *
     * sell[j] = Math.max(sell[j], buy[j]+prices[i])   , 1<=i<n && 1<=j<=k
     * buy[j] = Math.max(buy[j], sell[j-1]-prices[i])  , 1<=i<n && 1<=j<=k
     *
     * @param prices
     * @param k
     * @return
     */
    private int solution4(int[] prices, int k){
        int n = prices.length;
        k = Math.min(k, n/2);

        int[] buy = new int[k+1];
        int[] sell = new int[k+1];

        // init
        for(int j=1; j<=k; j++){
            sell[j] = 0;
            buy[j] = -prices[0];
        }

        for(int i=1; i<n; i++){
            for(int j=1; j<=k; j++){
                sell[j] = Math.max(sell[j], buy[j]+prices[i]);
                // 此处 sell[j-1] = sell[i][j-1](实际需要的sell[i-1][j-1]已被覆盖, 但是不影响最终结果)

                //    sell[i][j] = Math.max(sell[i-1][j], buy[i-1][j]+prices[i])
                // => sell[i][j-1] = Math.max(sell[i-1][j-1], buy[i-1][j-1]+prices[i])
                // => sell[j-1] = Math.max(sell[i-1][j-1], buy[i-1][j-1]+prices[i])
                // => sell[j-1]-prices[i] = Math.max(sell[i-1][j-1]-prices[i], buy[i-1][j-1]+prices[i]-prices[i])
                //                        = Math.max(sell[i-1][j-1]-prices[i], buy[i-1][j-1])
                // buy[j] = Math.max(buy[j], sell[j-1]-prices[i])
                //        = Math.max(buy[i-1][j], sell[j-1]-prices[i])
                //        = Math.max(buy[i-1][j], sell[i-1][j-1]-prices[i], buy[i-1][j-1])
                //        = Math.max(buy[i-1][j], sell[i-1][j-1]-prices[i]) [buy[i-1][j] >= buy[i-1][j-1]]
                // 可见, 与solution3中二维数组关系式是相同的
                buy[j] = Math.max(buy[j], sell[j-1]-prices[i]);
            }
        }

        return sell[k];
    }

    /**
     * 动态规划
     * 
     * dp[j]表示前j天股票价格某次交易后的最大收益
     * 
     *         { Math.max(maxProfit, dp[j]-prices[j])  , 买入
     * dp[j] = {
     *         { Math.max(maxProfit, dp[j]+prices[j])  , 卖出
     * 
     * @param prices
     * @param k
     * @return
     */
    private int solution5(int[] prices, int k){
        int n = prices.length;
        k = Math.min(k, n/2);

        int[] dp = new int[n];

        int maxProfit;
        for(int i=1; i<=k; i++){
            // 买入
            maxProfit = Integer.MIN_VALUE;
            for(int j=0; j<n; j++){
                dp[j] = Math.max(maxProfit, dp[j]-prices[j]);
                maxProfit = Math.max(maxProfit, dp[j]);
            }

            // 卖出
            maxProfit = Integer.MIN_VALUE;
            for(int j=0; j<n; j++){
                dp[j] = Math.max(maxProfit, dp[j]+prices[j]);
                maxProfit = Math.max(maxProfit, dp[j]);
            }
        }

        return dp[n-1];
    }
}