java算法-动态规划股票问题(一套公式秒杀6种问题)

参考:团灭 LeetCode 股票买卖问题-labuladong

股票问题

dp[i][k][0] 定义:第i天,手上不持有股票,至今至多进行k次交易
状态转移方程:

dp[i][k][0] = Math.max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);
dp[i][k][1] = Math.max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);

base case

dp[-1][k][0] = 0;
//解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0。
dp[-1][k][1] = Integer.MIN_VALUE;
//解释:还没开始的时候,是不可能持有股票的,⽤负⽆穷表⽰这种不可能。
dp[i][0][0] = 0
//解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0。
dp[i][0][1] = -infinity
//解释:不允许交易的情况下,是不可能持有股票的,⽤负⽆穷表⽰这种不可能。
121. 买卖股票的最佳时机

java算法-动态规划股票问题(一套公式秒杀6种问题)_第1张图片
框架

// k = 1
dp[i][1][0] = Math.max(dp[i-1][1][0],dp[i-1][1][1]+prices[i]);
dp[i][1][1] = Math.max(dp[i-1][1][1],dp[i-1][0][0]-prices[i])
			= Math.max(dp[i-1][1][1],-prices[i]);
//与k无关继续化简
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1] = Math.max(dp[i-1][1],-prices[i]);
//base case即 i=0
dp[0][0] = 0;
dp[0][1] = -prices[0];

完整代码

public int maxProfit_k_1(int[] prices){
        int n = prices.length;
        if (n==0) return 0;
        int[][] dp = new int[n][2];
        for (int i = 0; i < n; i++) {
            if (i==0){
                dp[0][0] = 0;
                dp[0][1] = -prices[0];
                continue;
            }
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
            dp[i][1] = Math.max(dp[i-1][1],-prices[i]);
        }
        return dp[n-1][0];
    }

注意到,状态转移⽅程的新状态只和相邻的⼀个状态有关,其实不⽤整个 dp 数组,只需要⼀个变量储存相邻的那个状态就⾜够了,这样可以把空间复杂度降到 O(1):

public int maxProfit_k_1(int[] prices){
        int n = prices.length;
        if (n==0) return 0;
        // dp[-1][0] = 0
        int dp0 = 0;
        // dp[-1][1] = Integer.MIN_VALUE
        int dp1 = Integer.MIN_VALUE;
        for (int price : prices) {
            dp0 = Math.max(dp0,dp1+price);
            dp1 = Math.max(dp1,-price);
        }
        return dp0;
    }

122. 买卖股票的最佳时机 II

java算法-动态规划股票问题(一套公式秒杀6种问题)_第2张图片
解析: 如果 k 为正⽆穷,那么就可以认为 k 和 k - 1 是⼀样的(与k无关,约掉k)。可以这样改写框架:

dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);

完整代码:

public int maxProfit_infi(int[] prices){
        int n = prices.length;
        if (n==0) return 0;
        int[][] dp = new int[n][2];
        for (int i = 0; i < n; i++) {
            if (i==0){
                dp[i][0] = 0;
                dp[i][1] = -prices[i];
                continue;
            }
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
        }
        return dp[n-1][0];
    }

309. 最佳买卖股票时机含冷冻期

java算法-动态规划股票问题(一套公式秒杀6种问题)_第3张图片
解析: 第 i 天选择 buy 的时候,要从 i-2 的状态转移,⽽不是 i-1 。

dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1] = Math.max(dp[i-1][1],dp[i-2][0]-prices[i]);

完整代码

public int maxProfit(int[] prices) {
        int n = prices.length;
        if (n==0) return 0;
        int[][] dp = new int[n][2];
        for (int i = 0; i < n; i++) {
            if (i==0){
                dp[0][0] = 0;
                dp[0][1] = -prices[0];
                continue;
            }
            if (i==1){
                dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
                dp[i][1] = Math.max(dp[i-1][1],-prices[i]);
                continue;
            }
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-2][0]-prices[i]);
        }
        return dp[n-1][0];
    }

714. 买卖股票的最佳时机含手续费

java算法-动态规划股票问题(一套公式秒杀6种问题)_第4张图片
解析: 每次交易要⽀付⼿续费,只要把⼿续费从利润中减去即可。改写⽅程:

dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]-fee);

完整代码

public int maxProfit_fee(int[] prices, int fee) {
        int n = prices.length;
        if (n== 0 ) return 0;
        int[][] dp = new int[n][2];
        for (int i = 0; i < n; i++) {
            if (i==0){
                dp[i][0] = 0;
                dp[i][1] = -prices[i]-fee;
                continue;
            }
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]-fee);
        }
        return dp[n-1][0];
    }

123. 买卖股票的最佳时机 III

java算法-动态规划股票问题(一套公式秒杀6种问题)_第5张图片
解析:
k = 2 和前⾯题⽬的情况稍微不同,因为上⾯的情况都和 k 的关系不太⼤。
要么 k 是正⽆穷,状态转移和 k 没关系了;要么 k = 1,跟 k = 0 这个 basecase 挨得近,最后也没有存在感。
所以我们必须穷举所有状态

dp[i][2][0] = Math.max(dp[i-1][2][0],dp[i-1][2][1]+prices[i]);
dp[i][2][1] = Math.max(dp[i-1][2][1],dp[i-1][1][0]-prices[i]);
dp[i][1][0] = Math.max(dp[i-1][1][0],dp[i-1][1][1]+prices[i]);
dp[i][1][1] = Math.max(dp[i-1][1][1],-prices[i]);

完整代码

public int maxProfit_k_2(int[] prices){
        int n = prices.length;
        if (n==0) return 0;
        int[][][] dp = new int[n][2+1][2];
        for (int i = 0; i < n; i++) {
            if (i==0){
                dp[i][2][0] = 0;
                dp[i][2][1] = -prices[i];
                dp[i][1][0] = 0;
                dp[i][1][1] = -prices[i];
                continue;
            }
            dp[i][2][0] = Math.max(dp[i-1][2][0],dp[i-1][2][1]+prices[i]);
            dp[i][2][1] = Math.max(dp[i-1][2][1],dp[i-1][1][0]-prices[i]);
            dp[i][1][0] = Math.max(dp[i-1][1][0],dp[i-1][1][1]+prices[i]);
            dp[i][1][1] = Math.max(dp[i-1][1][1],-prices[i]);
        }
        return dp[n-1][2][0];
    }

可降低空间复杂度

public int maxProfit_k_2(int[] prices) {
        
        int dp_i21 = Integer.MIN_VALUE;
        int dp_i20 = 0;
        int dp_i11 = Integer.MIN_VALUE;
        int dp_i10 = 0;
        for (int price : prices) {
            dp_i20 = Math.max(dp_i20,dp_i21+price);
            dp_i21 = Math.max(dp_i21,dp_i10-price);
            dp_i10 = Math.max(dp_i10,dp_i11+price);
            dp_i11 = Math.max(dp_i11,-price);

        }
        return dp_i20;
    }

188. 买卖股票的最佳时机 IV

java算法-动态规划股票问题(一套公式秒杀6种问题)_第6张图片
**解析:**有了上⼀题 k = 2 的铺垫,这题应该和上⼀题的第⼀个解法没啥区别。但是出现了⼀个超内存的错误,原来是传⼊的 k 值会⾮常⼤,dp 数组太⼤了。现在想想,交易次数 k 最多有多⼤呢?
⼀次交易由买⼊和卖出构成,⾄少需要两天。所以说有效的限制 k 应该不超过 n/2,如果超过,就没有约束作⽤了,相当于 k = +infinity。这种情况是之前解决过的。
直接把之前的代码重⽤:

public int maxProfit(int k_max, int[] prices) {
        int n = prices.length;
        if (n==0) return 0;
        if (k_max>n/2) return maxProfit_infi(prices);
        int[][][] dp = new int[n][k_max+1][2];
        for (int i = 0; i < n; i++) {
            for (int k = 0; k < k_max+1; k++) {
                if (i==0){
                    dp[0][k][0] = 0;
                    dp[0][k][1] = -prices[0];
                    continue;
                }
                if (k==0){
                    dp[i][k][0] = 0;
                    dp[i][k][1] = -prices[i];
                    continue;
                }
                dp[i][k][0] = Math.max(dp[i-1][k][0],dp[i-1][k][1]+prices[i]);
                dp[i][k][1] = Math.max(dp[i-1][k][1],dp[i-1][k-1][0]-prices[i]);
            }

        }
        return dp[n-1][k_max][0];
    }
    public int maxProfit_infi(int[] prices) {
        int[] dp = new int[2];
        dp[0] = 0;
        dp[1] = -prices[0];
        for (int price : prices) {
            dp[0] = Math.max(dp[0],dp[1]+price);
            dp[1] = Math.max(dp[1],dp[0]-price);
        }
        return dp[0];
    }
总结

⽤⼀个状态转移⽅程即可秒杀 6 道股票买卖问题

参考:团灭 LeetCode 股票买卖问题-labuladong

你可能感兴趣的:(算法)