Nothing lose,nothing gain.

拿捏动态规划之买卖股票

动态规划五步走(From 代码随想录)

  1. 确定dp数组以及下标的含义

  2. 确定递推公式 即状态转移方程

  3. 初始化dp数组

  4. 确定遍历顺序:

    以背包问题为例:先遍历背包 再遍历物品 则会出现拿多次相同物品,只是顺序不同 (序列数)
    先遍历物品再遍历背包则不会出现 (组合数)

  5. 举例推导dp数组

    每次对结果有疑问的时候就把dp数组打印出来,看看跟自己想的是不是有出入

正片开始

买卖股票的最佳时机

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        // dp[i][0]含义:当前这一天持股的利润
        // dp[i][1]含义:当前这一天不持股的利润
        // 状态转移方程: dp[i][0] = Math.max(dp[i - 1][0], - prices[i]);
        //              dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
        int[][] dp = new int[n][2];
        // 初始化
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for(int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], - prices[i]); // 找哪一天买入最便宜
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]); // 找哪一天卖出最贵
        }
        // 返回最后一天不持股的状态
        return dp[n - 1][1];
    }
}


买卖股票的最佳时机II

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        // 与I的区别为可以买卖多次
        // dp[i][0]含义: 当前这一天持有股票的利润
        // dp[i][1]含义: 当前这一天不持有股票的利润
        // 状态转移: dp[i][0] 在I题中因为只能买卖一次,所以只能从前面的dp[i - 1][0]和-prices[i]转移而来
        // 在本题中可以买卖多次 dp[i][0] 可以从 dp[i - 1][0] 和 dp[i - 1][1] - prices[i] (即前面买卖的最大利润 - 当前买入的价格) 转移而来
        // 所以 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]);
        int[][] dp = new int[n][2];
        // 初始化
        dp[0][0] = -prices[0];
        dp[0][1] = 0; // 刚开始的一天,前面都没有持股,所以也没有办法卖出,利润初始为0
        for(int i = 1; i < n; i++) {
            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][1];
    }
}


买卖股票的最佳时机III

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        // dp[i][0]: 第一次买入    dp[i][1]: 第一次卖出    dp[i][2]: 第二次买入   dp[i][3]:第二次卖出
        // dp[i][2]的状态需要从dp[i - 1][1]转移而来 dp[i][3]的状态需要从dp[i - 1][2]转移而来
        // 状态转移方程: dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);
        // dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
        // dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] - prices[i]);
        // dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] + prices[i]);
        int[][] dp = new int[n][4];
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        dp[0][2] = dp[0][0];
        dp[0][3] = dp[0][1];

        for(int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], -prices[i]);        
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
            dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] - prices[i]);
            dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] + prices[i]);
        }
        return dp[n - 1][3];
    }
}


买卖股票的最佳时机IV

class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        int kLen = k * 2;
        if(n == 0 || k == 0) return 0;
        // dp[i][j]含义:
        //             j为偶数(买入): 当前时间买入股票的利润 
        //             j为奇数(卖出): 当前时间卖出股票的利润
        // 状态转移方程: 1.前一天的状态继续不变   2.从前一天的 买入/卖出 状态变化为 卖出/买入
        //          dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] +/- prices[i]);
        int[][] dp = new int[n][kLen];
        // 初始化
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        // 可以直接全部初始化为 -prices[0] 和 0  但是为了更好理解 用了一个循环
        // 即 第1天第n次买入时的利润为 前n - 1次的利润 - 当前价格
        // 第1天第n次卖出时的利润为 前n - 1次的利润 + 这次操作的利润
        // 但是这些都在同一天发生,所以利润均为0
        for(int i = 2; i < kLen; i++) {
            dp[0][i] = dp[0][i - 2];
        }

        for(int i = 1; i < n; i++) {
            for(int j = 0; j < kLen; j++) {
                if(j == 0) dp[i][j] = Math.max(dp[i - 1][j], -prices[i]);
                else if(j % 2 == 0) dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]); // 持有
                else dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i]); // 不持有
            }
        }
        return dp[n - 1][kLen - 1];
    }
}


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

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        // dp[i][0] 当前持有股票的最大利润
        // dp[i][1] 当前不持有股票(可购买)的最大利润
        // dp[i][2] 当前不持有股票(在冷冻期)的最大利润

        // dp[i][0] 只能从 1.前一天就是持有  2.前一天为冷冻期,这一天买入
        // 所以 dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i]);
        // dp[i][1] 只能从 1.前一天就不持有股票  2.这一天将股票卖出
        // dp[i][2] 说明前一天必须是卖出 所以只能从dp[i - 1][1]转移而来
        // 状态转移方程: dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i]);
        //              dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
        //              dp[i][2] = dp[i - 1][1];
        int[][] dp = new int[n][3];
        // 初始化
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        dp[0][2] = 0;
        for(int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
            dp[i][2] = dp[i - 1][1];
        }
        // 为什么直接返回dp[n - 1][1]: 因为有可能会在最后一天卖出, dp[n - 1][1]的利润永远是最高的 
        return dp[n - 1][1];
    }
}


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

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        // dp[i][0] 表示当前持有股票的最大利润
        // dp[i][1] 表示当前不持有股票的最大利润
        // 状态转移方程(包括减去手续费): dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i] - fee);
        //                            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
        int[][] dp = new int[n][2];
        // 初始化
        dp[0][0] = -prices[0] - fee;  // 第一次买入也需要手续费
        dp[0][1] = 0;

        for(int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i] - fee);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
        }
        return dp[n - 1][1];
    }
}
点赞

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注