代码随想录 动态规划-股票问题

news2024/9/25 17:14:48

目录

121.买卖股票的最佳时机 

122买卖股票的最佳时机II

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

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

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

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


121.买卖股票的最佳时机 

121. 买卖股票的最佳时机

简单

给定一个数组 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 <= 105
  • 0 <= prices[i] <= 104

动规五部曲分析如下:

确定dp数组(dp table)以及下标的含义

dp[i][0] 表示第i天持有股票所得最多现金 ,这里可能有同学疑惑,本题中只能买卖一次,持有股票之后哪还有现金呢?

其实一开始现金是0,那么加入第i天买入股票现金就是 -prices[i], 这是一个负数。

dp[i][1] 表示第i天不持有股票所得最多现金

注意这里说的是“持有”,“持有”不代表就是当天“买入”!也有可能是昨天就买入了,今天保持持有的状态

很多同学把“持有”和“买入”没区分清楚。

在下面递推公式分析中,我会进一步讲解。

确定递推公式

如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来

第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]

第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]

那么dp[i][0]应该选所得现金最大的,所以dp[i][0] = max(dp[i - 1][0], -prices[i]);

如果第i天不持有股票即dp[i][1], 也可以由两个状态推出来

第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]

第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1][0]

同样dp[i][1]取最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);

这样递推公式我们就分析完了

dp数组如何初始化

由递推公式 dp[i][0] = max(dp[i - 1][0], -prices[i]); 和 dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);可以看出

其基础都是要从dp[0][0]和dp[0][1]推导出来。

那么dp[0][0]表示第0天持有股票,此时的持有股票就一定是买入股票了,因为不可能有前一天推出来,所以dp[0][0] -= prices[0];

dp[0][1]表示第0天不持有股票,不持有股票那么现金就是0,所以dp[0][1] = 0;

确定遍历顺序

从递推公式可以看出dp[i]都是由dp[i - 1]推导出来的,那么一定是从前向后遍历。

举例推导dp数组

以示例1,输入:[7,1,5,3,6,4]为例,dp数组状态如下:

121.买卖股票的最佳时机

dp[5][1]就是最终结果。

class Solution {  
    // 定义一个公开方法maxProfit,接受一个整数数组prices作为参数,返回最大利润  
    public int maxProfit(int[] prices) {  
        // 创建一个二维数组dp,大小为prices数组的长度乘以2  
        // dp[i][0]表示第i天不持有股票时的最大利润  
        // dp[i][1]表示第i天持有股票时的最大利润  
        int[][] dp = new int[prices.length][2];  
          
        // 初始化dp数组  
        // 第0天不持有股票的最大利润为0(因为没有股票可以卖)  
        dp[0][1] = 0;  
        // 第0天持有股票的最大利润为-prices[0](需要买入股票)  
        dp[0][0] = -prices[0];  
  
        // 从第1天开始遍历到最后一天  
        for(int i = 1; i < prices.length; i++){  
            // 第i天持有股票的最大利润要么是前一天就持有股票(dp[i - 1][1]),要么是在第i天买入股票(dp[i - 1][0] + prices[i])  
            dp[i][1] = Math.max(dp[i - 1][1],dp[i - 1][0] + prices[i]);  
  
            // 第i天不持有股票的最大利润要么是前一天就不持有股票(dp[i - 1][0]),要么是在第i天卖出股票(dp[i - 1][1] - prices[i])  
            // 注意:这里原代码有误,应该是dp[i - 1][1] - prices[i],而不是dp[i - 1][0] - prices[i]  
            // 正确的应该是:dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);  
            dp[i][0] = Math.max(dp[i - 1][0],- prices[i]);  
        }  
  
        // 返回最后一天持有股票时的最大利润,因为最后一天持有股票意味着在最后一天之前已经卖出了股票,从而获得最大利润  
        return dp[prices.length - 1][1];  
    }  
}

122买卖股票的最佳时机II

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

中等

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

示例 1:

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

示例 2:

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

示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0 。

提示:

  • 1 <= prices.length <= 3 * 104
  • 0 <= prices[i] <= 104

本题和121. 买卖股票的最佳时机 (opens new window)的唯一区别是本题股票可以买卖多次了(注意只有一只股票,所以再次购买前要出售掉之前的股票)

在动规五部曲中,这个区别主要是体现在递推公式上,其他都和121. 买卖股票的最佳时机 (opens new window)一样一样的

dp数组的含义:

  • dp[i][0] 表示第i天持有股票所得现金。
  • dp[i][1] 表示第i天不持有股票所得最多现金

如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来

  • 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
  • 第i天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i]

注意这里和121. 买卖股票的最佳时机 (opens new window)唯一不同的地方,就是推导dp[i][0]的时候,第i天买入股票的情况

在121. 买卖股票的最佳时机 (opens new window)中,因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]。

而本题,因为一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润。

那么第i天持有股票即dp[i][0],如果是第i天买入股票,所得现金就是昨天不持有股票的所得现金 减去 今天的股票价格 即:dp[i - 1][1] - prices[i]。

再来看看如果第i天不持有股票即dp[i][1]的情况, 依然可以由两个状态推出来

  • 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
  • 第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1][0]

注意这里和121. 买卖股票的最佳时机 (opens new window)就是一样的逻辑,卖出股票收获利润(可能是负值)天经地义!

class Solution {  
    // 定义一个公共方法,接收一个整数数组prices,返回最大利润  
    public int maxProfit(int[] prices) {  
        // 定义一个二维数组dp,dp[i][0]表示第i天不持有股票时的最大利润,dp[i][1]表示第i天持有股票时的最大利润  
        int[][] dp = new int[prices.length][2];  
          
        // 初始化dp数组  
        // 第0天不持有股票的最大利润是-prices[0](即买入了股票),持有股票的最大利润是0(即没有操作)  
        dp[0][0] = -prices[0];  
        dp[0][1] = 0;  
  
        // 从第1天开始遍历到最后一天  
        for(int i = 1; i < prices.length; i++){  
            // 第i天不持有股票的最大利润,有两种可能:  
            // 1. 第i-1天就不持有股票,第i天没有进行任何操作;  
            // 2. 第i-1天持有股票,第i天卖出股票,此时利润为dp[i-1][1] + prices[i]  
            // 取这两种情况的最大值  
            dp[i][0] = Math.max(dp[i - 1][1] - prices[i], dp[i - 1][0]);  
  
            // 第i天持有股票的最大利润,也有两种可能:  
            // 1. 第i-1天就不持有股票,第i天买入股票,此时利润为dp[i-1][0] - prices[i]  
            // 2. 第i-1天就持有股票,第i天没有进行任何操作  
            // 取这两种情况的最大值  
            dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]);  
        }  
  
        // 最后一天的最大利润就是dp[prices.length - 1][1]和dp[prices.length - 1][0]中的较大值  
        // 但由于题目中允许最后一天卖出股票,因此最后一天的最大利润必然是dp[prices.length - 1][1]  
        return dp[prices.length - 1][1];  
    }  
}

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

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

困难

给定一个数组,它的第 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。

示例 4:

输入:prices = [1]
输出:0

提示:

  • 1 <= prices.length <= 105
  • 0 <= prices[i] <= 105

动态规划五部曲详细分析一下:

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

一天一共就有五个状态,

  1. 没有操作 (其实我们也可以不设置这个状态)
  2. 第一次持有股票
  3. 第一次不持有股票
  4. 第二次持有股票
  5. 第二次不持有股票

dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。

需要注意:dp[i][1],表示的是第i天,买入股票的状态,并不是说一定要第i天买入股票,这是很多同学容易陷入的误区

例如 dp[i][1] ,并不是说 第i天一定买入股票,有可能 第 i-1天 就买入了,那么 dp[i][1] 延续买入股票的这个状态。

  1. 确定递推公式

达到dp[i][1]状态,有两个具体操作:

  • 操作一:第i天买入股票了,那么dp[i][1] = dp[i-1][0] - prices[i]
  • 操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]

那么dp[i][1]究竟选 dp[i-1][0] - prices[i],还是dp[i - 1][1]呢?

一定是选最大的,所以 dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);

同理dp[i][2]也有两个操作:

  • 操作一:第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
  • 操作二:第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]

所以dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])

同理可推出剩下状态部分:

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]);

  1. dp数组如何初始化

第0天没有操作,这个最容易想到,就是0,即:dp[0][0] = 0;

第0天做第一次买入的操作,dp[0][1] = -prices[0];

第0天做第一次卖出的操作,这个初始值应该是多少呢?

此时还没有买入,怎么就卖出呢? 其实大家可以理解当天买入,当天卖出,所以dp[0][2] = 0;

第0天第二次买入操作,初始值应该是多少呢?应该不少同学疑惑,第一次还没买入呢,怎么初始化第二次买入呢?

第二次买入依赖于第一次卖出的状态,其实相当于第0天第一次买入了,第一次卖出了,然后再买入一次(第二次买入),那么现在手头上没有现金,只要买入,现金就做相应的减少。

所以第二次买入操作,初始化为:dp[0][3] = -prices[0];

同理第二次卖出初始化dp[0][4] = 0;

  1. 确定遍历顺序

从递归公式其实已经可以看出,一定是从前向后遍历,因为dp[i],依靠dp[i - 1]的数值。

  1. 举例推导dp数组

以输入[1,2,3,4,5]为例

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

大家可以看到红色框为最后两次卖出的状态。

现在最大的时候一定是卖出的状态,而两次卖出的状态现金最大一定是最后一次卖出。如果想不明白的录友也可以这么理解:如果第一次卖出已经是最大值了,那么我们可以在当天立刻买入再立刻卖出。所以dp[4][4]已经包含了dp[4][2]的情况。也就是说第二次卖出手里所剩的钱一定是最多的。

所以最终最大利润是dp[4][4]

 

class Solution {  
    public int maxProfit(int[] prices) {  
        // 定义一个二维数组dp,其中dp[i][j]表示第i天进行j次交易时的最大利润  
        // j的取值范围是0到4,分别表示:  
        // 0: 未进行任何交易  
        // 1: 第一次买入股票  
        // 2: 第一次卖出股票  
        // 3: 第二次买入股票  
        // 4: 第二次卖出股票  
        int[][] dp = new int[prices.length][5];  
  
        // 初始化dp数组  
        // 第0天未进行任何交易,利润为0  
        dp[0][0] = 0;  
        // 第0天第一次买入股票,利润为-prices[0]  
        dp[0][1] = -prices[0];  
        // 第0天无法完成第一次卖出、第二次买入或第二次卖出,利润为0  
        dp[0][2] = 0;  
        dp[0][3] = -prices[0];  
        dp[0][4] = 0;  
  
        // 从第1天开始遍历到最后一天  
        for(int i = 1; i < prices.length; i++){  
            // 第i天未进行任何交易,利润与前一天相同  
            dp[i][0] = dp[i - 1][0];  
  
            // 第i天第一次买入股票,有两种可能:  
            // 1. 第i-1天未进行任何交易,第i天买入股票,利润为dp[i-1][0] - prices[i]  
            // 2. 第i-1天已经第一次买入股票,第i天未进行任何操作,利润与前一天相同  
            dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);  
  
            // 第i天第一次卖出股票,有两种可能:  
            // 1. 第i-1天第一次买入股票,第i天卖出股票,利润为dp[i-1][1] + prices[i]  
            // 2. 第i-1天已经第一次卖出股票,第i天未进行任何操作,利润与前一天相同  
            dp[i][2] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][2]);  
  
            // 第i天第二次买入股票,有两种可能:  
            // 1. 第i-1天第一次卖出股票,第i天再次买入股票,利润为dp[i-1][2] - prices[i]  
            // 2. 第i-1天已经第二次买入股票,第i天未进行任何操作,利润与前一天相同  
            // 注意:这里不允许在第一次买入之前直接进行第二次买入,所以dp[i-1][0]和dp[i-1][3]不参与比较  
            dp[i][3] = Math.max(dp[i - 1][2] - prices[i], dp[i - 1][3]);  
  
            // 第i天第二次卖出股票,有两种可能:  
            // 1. 第i-1天第二次买入股票,第i天卖出股票,利润为dp[i-1][3] + prices[i]  
            // 2. 第i-1天已经第二次卖出股票,第i天未进行任何操作,利润与前一天相同  
            // 注意:这里不允许在第一次卖出之前直接进行第二次卖出,所以dp[i-1][0]、dp[i-1][1]和dp[i-1][2]不参与比较  
            dp[i][4] = Math.max(dp[i - 1][3] + prices[i], dp[i - 1][4]);  
        }  
  
        // 最后一天的最大利润就是dp[prices.length - 1][4],即进行了两次交易后的最大利润  
        return dp[prices.length - 1][4];  
    }  
}

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

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

困难

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入:k = 2, prices = [2,4,1]
输出:2
解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。

示例 2:

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

提示:

  • 1 <= k <= 100
  • 1 <= prices.length <= 1000
  • 0 <= prices[i] <= 1000

动规五部曲,分析如下:

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

在动态规划:123.买卖股票的最佳时机III (opens new window)中,我是定义了一个二维dp数组,本题其实依然可以用一个二维dp数组。

使用二维数组 dp[i][j] :第i天的状态为j,所剩下的最大现金是dp[i][j]

j的状态表示为:

0 表示不操作

1 第一次买入

2 第一次卖出

3 第二次买入

4 第二次卖出

.....

大家应该发现规律了吧 ,除了0以外,偶数就是卖出,奇数就是买入

题目要求是至多有K笔交易,那么j的范围就定义为 2 * k + 1 就可以了。

所以二维dp数组的C++定义为:

vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));

确定递推公式

还要强调一下:dp[i][1],表示的是第i天,买入股票的状态,并不是说一定要第i天买入股票,这是很多同学容易陷入的误区

达到dp[i][1]状态,有两个具体操作:

操作一:第i天买入股票了,那么dp[i][1] = dp[i - 1][0] - prices[i]

操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]

选最大的,所以 dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1]);

同理dp[i][2]也有两个操作:

操作一:第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]

操作二:第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]

所以dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])

同理可以类比剩下的状态,代码如下:

// 遍历每一种可能的交易次数  
for(int j = 1; j < 2 * k + 1; j++){  
    // 如果j是奇数(即买入股票)  
    if(j %2 == 1){  
        // 当前的最大利润要么是不进行这次交易(即保持前一天的最大利润),  
        // 要么是进行这次交易(即前一天的利润减去今天的股票价格)  
        dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]);  
        } else {  
            // 如果j是偶数(即卖出股票)  
            // 当前的最大利润要么是不进行这次交易(即保持前一天的最大利润),  
            // 要么是进行这次交易(即前一天的利润加上今天的股票价格)  
            dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i]);  
    }  
}  

本题和动态规划:123.买卖股票的最佳时机III (opens new window)最大的区别就是这里要类比j为奇数是买,偶数是卖的状态

dp数组如何初始化

第0天没有操作,这个最容易想到,就是0,即:dp[0][0] = 0;

第0天做第一次买入的操作,dp[0][1] = -prices[0];

第0天做第一次卖出的操作,这个初始值应该是多少呢?

此时还没有买入,怎么就卖出呢? 其实大家可以理解当天买入,当天卖出,所以dp[0][2] = 0;

第0天第二次买入操作,初始值应该是多少呢?应该不少同学疑惑,第一次还没买入呢,怎么初始化第二次买入呢?

第二次买入依赖于第一次卖出的状态,其实相当于第0天第一次买入了,第一次卖出了,然后在买入一次(第二次买入),那么现在手头上没有现金,只要买入,现金就做相应的减少。

所以第二次买入操作,初始化为:dp[0][3] = -prices[0];

第二次卖出初始化dp[0][4] = 0;

所以同理可以推出dp[0][j]当j为奇数的时候都初始化为 -prices[0]

代码如下:

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

在初始化的地方同样要类比j为偶数是卖、奇数是买的状态

确定遍历顺序

从递归公式其实已经可以看出,一定是从前向后遍历,因为dp[i],依靠dp[i - 1]的数值。

举例推导dp数组

以输入[1,2,3,4,5],k=2为例。

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

最后一次卖出,一定是利润最大的,dp[prices.size() - 1][2 * k]即红色部分就是最后求解。

class Solution {  
    // 定义方法maxProfit,接收两个参数:k表示最多交易次数,prices表示股票价格数组  
    public int maxProfit(int k, int[] prices) {  
        // 创建一个二维数组dp,大小为prices的长度乘以2k+1  
        // dp[i][j]表示在第i天,进行j次交易(买入和卖出各算一次)时的最大利润  
        int[][] dp = new int[prices.length][2 * k + 1];  
  
        // 初始化:对于第0天,即dp[0]这一行,如果交易次数j是奇数(即买入股票)  
        // 则利润为-prices[0](因为买入了股票,所以利润减少)  
        for(int j = 1; j < 2 * k; j += 2){  
            dp[0][j] = -prices[0];  
        }  
  
        // 遍历每一天  
        for(int i = 1; i < prices.length; i++){  
            // 遍历每一种可能的交易次数  
            for(int j = 1; j < 2 * k + 1; j++){  
                // 如果j是奇数(即买入股票)  
                if(j %2 == 1){  
                    // 当前的最大利润要么是不进行这次交易(即保持前一天的最大利润),  
                    // 要么是进行这次交易(即前一天的利润减去今天的股票价格)  
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] - prices[i]);  
                } else {  
                    // 如果j是偶数(即卖出股票)  
                    // 当前的最大利润要么是不进行这次交易(即保持前一天的最大利润),  
                    // 要么是进行这次交易(即前一天的利润加上今天的股票价格)  
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] + prices[i]);  
                }  
            }  
        }  
  
        // 返回最后一天,进行2k次交易时的最大利润(即总的最大利润)  
        return dp[prices.length - 1][2 * k];  
    }  
}

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

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

中等

相关标签

相关企业

给定一个整数数组prices,其中第  prices[i] 表示第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: prices = [1,2,3,0,2]
输出: 3 
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

示例 2:

输入: prices = [1]
输出: 0

提示:

  • 1 <= prices.length <= 5000
  • 0 <= prices[i] <= 1000

动规五部曲,分析如下:

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

dp[i][j],第i天状态为j,所剩的最多现金为dp[i][j]。

其实本题很多同学搞的比较懵,是因为出现冷冻期之后,状态其实是比较复杂度,例如今天买入股票、今天卖出股票、今天是冷冻期,都是不能操作股票的。

具体可以区分出如下四个状态:

状态一:持有股票状态(今天买入股票,或者是之前就买入了股票然后没有操作,一直持有)

不持有股票状态,这里就有两种卖出股票状态

状态二:保持卖出股票的状态(两天前就卖出了股票,度过一天冷冻期。或者是前一天就是卖出股票状态,一直没操作)

状态三:今天卖出股票

状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天!

j的状态为:

0:状态一

1:状态二

2:状态三

3:状态四

如果大家按照代码随想录顺序来刷的话,会发现 买卖股票最佳时机 1,2,3,4 的题目讲解中

动态规划:121.买卖股票的最佳时机(opens new window)

动态规划:122.买卖股票的最佳时机II(opens new window)

动态规划:123.买卖股票的最佳时机III(opens new window)

动态规划:188.买卖股票的最佳时机IV(opens new window)

「今天卖出股票」我是没有单独列出一个状态的归类为「不持有股票的状态」,而本题为什么要单独列出「今天卖出股票」 一个状态呢?

因为本题我们有冷冻期,而冷冻期的前一天,只能是 「今天卖出股票」状态,如果是 「不持有股票状态」那么就很模糊,因为不一定是 卖出股票的操作。

如果没有按照 代码随想录 顺序去刷的录友,可能看这里的讲解 会有点困惑,建议把代码随想录本篇之前股票内容的讲解都看一下,领会一下每天 状态的设置。

注意这里的每一个状态,例如状态一,是持有股票股票状态并不是说今天一定就买入股票,而是说保持买入股票的状态即:可能是前几天买入的,之后一直没操作,所以保持买入股票的状态

确定递推公式

达到买入股票状态(状态一)即:dp[i][0],有两个具体操作:

操作一:前一天就是持有股票状态(状态一),dp[i][0] = dp[i - 1][0]

操作二:今天买入了,有两种情况

前一天是冷冻期(状态四),dp[i - 1][3] - prices[i]

前一天是保持卖出股票的状态(状态二),dp[i - 1][1] - prices[i]

那么dp[i][0] = max(dp[i - 1][0], dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]);

达到保持卖出股票状态(状态二)即:dp[i][1],有两个具体操作:

操作一:前一天就是状态二

操作二:前一天是冷冻期(状态四)

dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);

达到今天就卖出股票状态(状态三),即:dp[i][2] ,只有一个操作:

昨天一定是持有股票状态(状态一),今天卖出

即:dp[i][2] = dp[i - 1][0] + prices[i];

达到冷冻期状态(状态四),即:dp[i][3],只有一个操作:

昨天卖出了股票(状态三)

dp[i][3] = dp[i - 1][2];

综上分析,递推代码如下:

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

dp数组如何初始化

这里主要讨论一下第0天如何初始化。

如果是持有股票状态(状态一)那么:dp[0][0] = -prices[0],一定是当天买入股票。

保持卖出股票状态(状态二),这里其实从 「状态二」的定义来说 ,很难明确应该初始多少,这种情况我们就看递推公式需要我们给他初始成什么数值。

如果i为1,第1天买入股票,那么递归公式中需要计算 dp[i - 1][1] - prices[i] ,即 dp[0][1] - prices[1],那么大家感受一下 dp[0][1] (即第0天的状态二)应该初始成多少,只能初始为0。想一想如果初始为其他数值,是我们第1天买入股票后 手里还剩的现金数量是不是就不对了。

今天卖出了股票(状态三),同上分析,dp[0][2]初始化为0,dp[0][3]也初始为0。

确定遍历顺序

从递归公式上可以看出,dp[i] 依赖于 dp[i-1],所以是从前向后遍历。

举例推导dp数组

以 [1,2,3,0,2] 为例,dp数组如下:

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

最后结果是取 状态二,状态三,和状态四的最大值,不少同学会把状态四忘了,状态四是冷冻期,最后一天如果是冷冻期也可能是最大值。

class Solution {
    public int maxProfit(int[] prices) {
        // 创建一个二维数组dp,dp[i][j]表示在第i天结束时,处于状态j所能获得的最大利润
        // j=0表示持有股票,j=1表示不持有股票,处于冷冻期,j=2表示不持有股票,不处于冷冻期,j=3表示不持有股票,不处于冷冻期且卖出股票
        int[][] dp = new int[prices.length][4];
        // 初始情况:第一天持有股票的利润为负的股票价格
        dp[0][0] = -prices[0];
        for(int i = 1; i < prices.length; i++){
            // 状态转移方程:
            // 在第i天持有股票的利润为:前一天持有股票、不持有股票但不处于冷冻期、不持有股票且卖出股票中的最大值减去当天的股票价格
            dp[i][0] = Math.max(dp[i - 1][0], Math.max(dp[i - 1][1], dp[i - 1][3]) - prices[i]);
            // 在第i天不持有股票但处于冷冻期的利润为:前一天不持有股票但处于冷冻期、不持有股票不处于冷冻期但卖出股票的最大值
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][3]);
            // 在第i天不持有股票不处于冷冻期的利润为:前一天持有股票加上当天的股票价格
            dp[i][2] = dp[i - 1][0] + prices[i];
            // 在第i天不持有股票不处于冷冻期且卖出股票的利润为:前一天不持有股票不处于冷冻期的利润
            dp[i][3] = dp[i - 1][2];
        }
        // 最终答案为最后一天不持有股票的三种状态中的最大值
        return Math.max(dp[prices.length - 1][1], Math.max(dp[prices.length - 1][2], dp[prices.length - 1][3]));
    }
}

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

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

中等

提示

给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

示例 1:

输入:prices = [1, 3, 2, 8, 4, 9], fee = 2
输出:8
解释:能够达到的最大利润:  
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8

示例 2:

输入:prices = [1,3,7,5,10,3], fee = 3
输出:6

提示:

  • 1 <= prices.length <= 5 * 104
  • 1 <= prices[i] < 5 * 104
  • 0 <= fee < 5 * 104

相对于动态规划:122.买卖股票的最佳时机II (opens new window),本题只需要在计算卖出操作的时候减去手续费就可以了,代码几乎是一样的。

唯一差别在于递推公式部分,所以本篇也就不按照动规五部曲详细讲解了,主要讲解一下递推公式部分。

这里重申一下dp数组的含义:

dp[i][0] 表示第i天持有股票所省最多现金。 dp[i][1] 表示第i天不持有股票所得最多现金

如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来

  • 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
  • 第i天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i]

所以:dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);

在来看看如果第i天不持有股票即dp[i][1]的情况, 依然可以由两个状态推出来

  • 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
  • 第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金,注意这里需要有手续费了即:dp[i - 1][0] + prices[i] - fee

所以:dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);

本题和动态规划:122.买卖股票的最佳时机II (opens new window)的区别就是这里需要多一个减去手续费的操作

class Solution {  
    public int maxProfit(int[] prices, int fee) {  
        // 初始化动态规划数组,大小为prices长度乘以2  
        // dp[i][0]表示第i天不持有股票时的最大利润  
        // dp[i][1]表示第i天持有股票时的最大利润  
        int[][] dp = new int[prices.length][2];  
  
        // 初始化第一天,不持有股票时的最大利润为0,持有股票时的最大利润为-prices[0]  
        dp[0][0] = 0;  
        dp[0][1] = -prices[0];  
  
        // 遍历每一天  
        for(int i = 1; i < prices.length; i++){  
            // 第i天不持有股票时的最大利润  
            // 要么是前一天就不持有股票,今天没有进行任何操作(保持dp[i - 1][0]的值)  
            // 要么是前一天持有股票,今天卖出股票(利润为dp[i - 1][1] + prices[i]),但需要扣除交易费fee  
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);  
  
            // 第i天持有股票时的最大利润  
            // 要么是前一天就持有股票,今天没有进行任何操作(保持dp[i - 1][1]的值)  
            // 要么是前一天不持有股票,今天买入股票(利润为dp[i - 1][0] - prices[i])  
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);  
        }  
  
        // 返回最后一天的最大利润,即最后一天不持有股票时的最大利润  
        // 因为如果最后一天持有股票,那么无法在当天卖出获得利润  
        return dp[prices.length - 1][0];  
    }  
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1533784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【FLOOD FILL专题】【蓝桥杯备考训练】:扫雷、动态网格、走迷宫、画图、山峰和山谷【已更新完成】

目录 1、扫雷&#xff08;Google Kickstart2014 Round C Problem A&#xff09; 2、动态网格&#xff08;Google Kickstart2015 Round D Problem A&#xff09; 3、走迷宫&#xff08;模板&#xff09; 4、画图&#xff08;第六次CCF计算机软件能力认证&#xff09; 5、山…

Docker 安装 Skywalking以及UI界面

关于Skywalking 在现代分布式系统架构中&#xff0c;应用性能监控&#xff08;Application Performance Monitoring, APM&#xff09;扮演着至关重要的角色。本文将聚焦于一款备受瞩目的开源APM工具——Apache Skywalking&#xff0c;通过对其功能特性和工作原理的详细介绍&am…

cocos(困扰了我一晚上的代码)地图适配不同手机屏幕

Creator 版本&#xff1a;3.8.2 目标平台&#xff1a; 微信小游戏 有人反馈我的小游戏没有做屏幕适配。其实我是做了的。但是在不同手机分辨率的情况下。 适配出了问题。然后我改了一晚上的代码终于做好了。 设计的分辨率是960*640.也就是白色区域。 但是大部分手机是…

2024全国水科技大会:【协办单位】山东文远环保科技股份有限公司

山东文远环保科技股份有限公司坐落于千年古城齐国故都--临淄。初始成立于2011年&#xff0c;是淄博市首批国有资本参股的混合改制企业。 公司着力打造环保设备制造、环保工程及服务、环保水务/固废处理/新能源项目投资及运营管理、固废循环经济产业园等四大板块。是一家集投资、…

【spring】@Conditional注解学习

Conditional介绍 Conditional注解用于按照设定的条件进行判断&#xff0c;从而决定是否将某个bean注册到Spring容器中。 Conditional注解是在Spring 4.0版本中引入的&#xff0c;它提供了一种更加灵活的方式来控制bean的创建和注册。在此之前&#xff0c;开发者通常使用Profi…

【网络原理】HTTP 请求 (Request)详解

文章目录 &#x1f38d;请求格式&#x1f384;认识URL&#x1f338;query string&#x1f338;关于 URL encode &#x1f340;认识 “方法” (method)&#x1f338;GET方法&#x1f338;POST 方法&#x1f338;GET 和 POST 的区别 &#x1f332;认识请求 “报头” (header)&…

cocos控制物体移动轨迹

引擎&#xff1a;CocosCreator 3.8.2 思考了很久决定要不要发这票文章。因为毕竟我也是一个新手。但是这个问题真的困扰了我很久。 特意分享给大家。 如图所示有2个方块。他们可以跟着轨迹移动。 这个轨迹箱子总体来说有以下几个规则。 1.只能沿着轨迹移动。 2.上面只能有一…

[ C++ ] STL---string类的模拟实现

目录 string类的成员变量 构造函数 有参构造函数 无参构造函数 析构函数 拷贝构造函数 赋值运算符重载 string类对象的容量操作 string类对象的遍历与访问 [ ] 下标遍历 迭代器遍历 string类对象的增删查改 string类的成员变量 string类底层为动态开辟的字符数组&a…

提升水库大坝安全与效率:现代技术云平台的应用

在我国&#xff0c;水库大坝的数量居世界之首&#xff0c;它们在推动国民经济发展中扮演着不可或缺的角色。然而&#xff0c;要想让这些水利工程充分发挥其价值&#xff0c;不仅需要精准的调度与高效的管理&#xff0c;更重要的是要确保其安全无虞。一旦发生事故&#xff0c;后…

【机器学习】基于变色龙算法优化的BP神经网络分类预测(SSA-BP)

目录 1.原理与思路2.设计与实现3.结果预测4.代码获取 1.原理与思路 【智能算法应用】智能算法优化BP神经网络思路【智能算法】变色龙优化算法&#xff08;CSA)原理及实现 2.设计与实现 数据集&#xff1a; 数据集样本总数2000 多输入多输出&#xff1a;样本特征24&#xff…

Java中的I/O讲解(超容易理解)(上篇)

如果想观看更多Java内容 可上我的个人主页关注我&#xff0c;地址子逸爱编程-CSDN博客https://blog.csdn.net/a15766649633?spm1000.2115.3001.5343使用工具 IntelliJ IDEA Community Edition 2023.1.4 使用语言 Java8 代码能力快速提升小方法&#xff0c;看完代码自己敲一…

智慧乡村赋能发展:数字乡村推动农村经济社会持续繁荣

目录 一、智慧乡村的内涵与发展意义 二、智慧乡村赋能发展的路径 1、加强信息基础设施建设 2、推进农业生产智能化 3、提升乡村治理现代化水平 4、推动农村产业融合发展 三、智慧乡村发展面临的挑战与对策 四、智慧乡村发展的未来展望 1、技术融合创新将更加深入 2、…

百度智能云+SpringBoot=AI对话【人工智能】

百度智能云SpringBootAI对话【人工智能】 前言版权推荐百度智能云SpringBootAI对话【人工智能】效果演示登录AI对话 项目结构后端开发pom和propertiessql_table和entitydao和mapperservice和implconfig和utilLoginController和ChatController 前端开发css和jslogin.html和chat.…

怎么在Linux系统下Docker部署Excalidraw白板工具并实现无公网IP远程访问?

文章目录 1. 安装Docker2. 使用Docker拉取Excalidraw镜像3. 创建并启动Excalidraw容器4. 本地连接测试5. 公网远程访问本地Excalidraw5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定公网地址远程访问 本文主要介绍如何在Ubuntu系统使用Docker部署开源白板工具Excal…

【Linux】实现进度条小程序

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 前言2. 回车和换行3. 缓冲区4. 进度条4.1 倒计时设置4.2 进度条4.2.1 实现简单进度条4.2.2 进度条完善 5. 附进度条代码5.1 Processbar.h5.2 Processbar.c5.3 Main.c5.4 Makefile 1. 前言 在之前已经了解了 【Lin…

C++ list详解及模拟实现

目录 本节目标 1. list的介绍及使用 1.2 list的使用 2.list的模拟实现 1.对list进行初步的实现 2.头插和任意位置的插入 3.pos节点的删除&#xff0c;头删&#xff0c;尾删 4.销毁list和析构函数 5.const迭代器 6.拷贝构造和赋值操作 3.完整代码 本节目标 1. list的…

怎么做扫码签到活动_一场别开生面的活动签到革新之旅

在数字化飞速发展的今天&#xff0c;传统的签到方式已无法满足人们对高效、便捷、互动性的追求。为此&#xff0c;我们创新性地推出了扫码签到活动&#xff0c;为您带来一场前所未有的智慧互动新体验。 工具/原料 微信小程序 飞多多网站 方法/步骤 一、扫码签到&#xff0c…

【目标跟踪】奇葩需求如何处理(二)

文章目录 一、前言二、奇葩需求2.1、井盖2.2、管线 三、后记 一、前言 在工作中往往出现些奇葩需求。上一篇介绍了一些奇葩需求奇葩需求如何处理&#xff08;一&#xff09; &#xff0c;今天给大家分享一些更奇葩的需求。 二、奇葩需求 2.1、井盖 昨天突然接到一个需求&…

JAVA_会话

会话技术 1.会话: 一次会话包含多次请求和响应 2.功能: 在一次会话的范围内的多次请求&#xff0c;共享数据 3.方式: 3.1.客户端会话技术 Cookie(甜点) 1.概念: 客户端会话技术,将数据保存到客户端 2.快速入门: 1.创建Cookie对象,绑定数据new Cookie(String name,String v…

msvcp140.dll是什么文件?msvcp140.dll丢失如何解决(最新教程)

在玩电脑的时候&#xff0c;经常会碰到一些烦人的东西&#xff0c;比如那个“msvcp140.dll丢失”啥啥啥的。这个东西一出现&#xff0c;整个人都不好了&#xff0c;完全影响了我们愉快电脑生活的节奏。为啥会出现msvcp140.dll丢失的这种情况&#xff0c;怎么解决&#xff0c;还…