【动态规划】买卖股票的最佳日期,动态规划,贪心+动态规划,动态规划

371 阅读5分钟

文章目录

leetcode121 买卖股票的最佳日期① 动态规划

特征方程:dp[i]=Math.max(dp[i-1],prices[i]-minPrice);
初始化 dp[0]=0 dp[1]=0 dp[2]开始执行特征方程

题目描述

题目描述:给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。

注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

1 <= prices.length <= 10^5
0 <= prices[i] <= 10^4

解法:动态规划 dp[i]=max(dp[i−1],prices[i]−minprice)

动态规划一般分为一维、二维、多维(使用状态压缩),对应形式为 dp(i)dp(i)、dp(i)(j)dp(i)(j)、二进制dp(i)(j)二进制dp(i)(j)。

  1. 明确 dp(i)dp(i) 应该表示什么(二维情况:dp(i)(j)dp(i)(j));
  2. 根据 dp(i)dp(i) 和 dp(i-1)dp(i−1) 的关系得出状态转移方程;
  3. 确定初始条件,如 dp(0)dp(0)。

dp[i]=max(dp[i−1],prices[i]−minprice)

彻底搞懂这个dp特征方程:

  1. dp[i] 表示第i天最大利润;
  2. dp[i-1] 表示和前一天的状态一样,当前未持有股票,所以前一天也未持有股票,所以和前一天一样。注意:第i天未持有股票可能有两种可能:尚未开始买入股票继续等待、已经卖出了买入的股票。
  3. prices[i]−minprice 表示当前持有股票,可以以今天的价格卖出,曾经以最低价格买入的。

两种情况中,取最大值。

只能买一次的话,第i天的状态只能有两种,第i天未持有股票,则i-1天也一定未持有股票,利润和第 i-1 天一样(可能完成了买入卖出操作,可能尚未开始买入卖出操作);第i天持有股票,则可以以今天的价格卖出,曾经以最低价格买入的。
在这个动态规划方程中,把两种情况都考虑到了,所以没问题。

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

因为dp[i]仅仅和dp[i-1]相关,直接简化,用变量maxProfit代替dp数组,如下:

class Solution {
    public int maxProfit(int[] prices) {
       if(prices.length==0) return 0;
       int minPrice=prices[0];
       int maxProfit=0;
       for(int i=1;i<prices.length;i++){
           minPrice=Math.min(minPrice,prices[i]);
           maxProfit=Math.max(maxProfit,prices[i]-minPrice);
       }
       return maxProfit;
    }
}

其实,对于这个动态规划方程,如果想看得更清楚一点,可以写成二维的,加上当前是否持有股票的判断,如下:

leetcode122 买卖股票的最佳日期② 贪心+动态规划

题目描述

题目描述:

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:
1 <= prices.length <= 3 * 10 ^ 4
0 <= prices[i] <= 10 ^ 4

解法1:贪心,吃掉所有上涨,一定是最大的,连涨的情况也被贪心包含了

具体代码为:

class Solution {
    public int maxProfit(int[] prices) {
       if(prices.length<2)
            return  0;
        int value=0;
       
        for(int i=1;i<prices.length;i++)
        {
            // 就要吃掉波动市场上的所有上涨,避开所有下跌
            if(prices[i]>prices[i-1])  
                value+=prices[i]-prices[i-1];
        }
        return  value;
    }
}

注意:连涨几天也没关系,虽然不可以“第一天买第二天卖,然后第二天买第三天卖”,但是可以第一天买第三天卖,一直累加到最后涨的那一天,刚好跌的前一天就好了。

解法2:动态规划

考虑到「不能同时参与多笔交易」,因此第i天交易结束后只可能存在两种:

  1. 手里有一支股票 dp[i][1]
  2. 没有股票的状态 dp[i][0]

不可能持有两支股票。

定义状态 dp[i][0] 表示第 i 天交易完后手里没有股票的最大利润,dp[i][1] 表示第 i 天交易完后手里持有一支股票的最大利润(i 从 0 开始)。

考虑 dp[i][0] 的转移方程,表示这一天交易完后手里没有股票:

  1. 可能前一天也没有股票,即 dp[i−1][0],表示继续等待
  2. 前一天结束的时候手里持有一支股票,即 dp[i−1][1],表示卖出股票,并获得 prices[i] 的收益

因此为了收益最大化,我们列出如下的转移方程:

dp[i][0]=max{dp[i−1][0],dp[i−1][1]+prices[i]}

这个动态规划方程,表示当前没有股票的前一天的两种情况的利润最大值。

再来考虑 dp[i][1],按照同样的方式考虑转移状态,那么可能的转移状态为

  1. 前一天已经持有一支股票,即 dp[i−1][1],表示继续持有
  2. 前一天结束时还没有股票,即 dp[i−1][0],表示买入股票,并减少 prices[i] 的本金

可以列出如下的转移方程:

dp[i][1]=max{dp[i−1][1],dp[i−1][0]−prices[i]}

这个动态规划方程,表示当前持有股票的前一天的两种情况的利润最大值。

股票在i天卖出就是+prices[i],股票在i天买入就是-prices[i];
继续持有或者继续未持有股票就是dp[i-1][0或1]。

对于初始状态,根据状态定义我们可以知道第 0 天交易结束的时候 dp[0][0] = 0,dp[0][1] = −prices[0]。

因此,我们只要从前往后依次计算状态即可。由于全部交易结束后,持有股票的收益一定低于不持有股票的收益(因为股票卖出了才赚钱,你也可以最后比较一下,取dp[n−1][0]和dp[n−1][1]中较为大的那个),最后的答案即为dp[n−1][0]。

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = 0;  // 初始化:第0天没有股票,收益为0
        dp[0][1] = -prices[0];  // 初始化:第0天买入股票,收益为-prices[0],或者手里本金为-prices[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][0];
    }
}

注意到上面的状态转移方程中,每一天的状态只与前一天的状态有关,而与更早的状态都无关,因此我们不必存储这些无关的状态,只需要将 dp[i−1][0] 和 dp[i−1][1] 存放在两个变量中,通过它们计算出 dp[i][0] 和 dp[i][1] 并存回对应的变量,以便于第 i+1 天的状态转移即可。

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int dp0 = 0, dp1 = -prices[0];
        for (int i = 1; i < n; ++i) {
            int newDp0 = Math.max(dp0, dp1 + prices[i]);
            int newDp1 = Math.max(dp1, dp0 - prices[i]);
            dp0 = newDp0;
            dp1 = newDp1;
        }
        return dp0;
    }
}

leetcode123 买卖股票的最佳时机③ 动态规划

题目描述

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。

示例 2:
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。

提示:
1 <= prices.length <= 10^5
0 <= prices[i] <= 10^5

解法:动态规划

二维dp数组

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if (prices.size() == 0) return 0;
        vector<vector<int>> dp(prices.size(), vector<int>(5, 0));
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        dp[0][3] = -prices[0];
        for (int i = 1; i < prices.size(); i++) {
            dp[i][0] = dp[i - 1][0];
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
            dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
            dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
            dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
        }
        return dp[prices.size() - 1][4];
    }
};

一维dp数组

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if (prices.size() == 0) return 0;
        vector<int> dp(5, 0);
        dp[0] = 0;
        dp[1] = -prices[0];
        dp[3] = -prices[0];
        for (int i = 1; i < prices.size(); i++) {
            dp[1] = max(dp[1], dp[0] - prices[i]);
            dp[2] = max(dp[2], dp[1] + prices[i]);
            dp[3] = max(dp[3], dp[2] - prices[i]);
            dp[4] = max(dp[4], dp[3] + prices[i]);
        }
        return dp[4];
    }
};

四个变量,表示四种状态

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int buy1 = -prices[0], sell1 = 0;
        int buy2 = -prices[0], sell2 = 0;
        for (int i = 1; i < n; ++i) {
            buy1 = Math.max(buy1, -prices[i]);
            sell1 = Math.max(sell1, buy1 + prices[i]);
            buy2 = Math.max(buy2, sell1 - prices[i]);
            sell2 = Math.max(sell2, buy2 + prices[i]);
        }
        return sell2;
    }
}