代码随想录算法训练营Day50 | 123. 买卖股票的最佳时机III | 188. 买卖股票的最佳时机IV

文章目录

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

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

题目链接 | 解题思路

本题比之前两题难很多,之前的题目确定了“买卖一次”或者“无限买卖”,而本题规定的是“最多买卖两次”,也就是说可以买卖一次、买卖两次、完全不进行买卖,显得非常复杂。然而,解题思路依然是最古老、最有效的分类讨论:先确定每一天有什么样的不同状态,然后对于每种状态进行递推。

比较明显的,每一天最多有五种状态:

  • 还没经过股票的买卖
    • 虽然这个状态是存在并且合理的,但是我们可以确定,这个状态下的净利润必然是 0,其实也就没有递推的意义
  • 第一次持有股票
  • 第一次未持有股票(买入过股票之后)
  • 第二次持有股票
  • 第二次未持有股票(买入过股票之后)
  1. dp 数组的下标含义:
    • dp[i][0]:第 i 天结束第一次持有股票的最大净利润
    • dp[i][1]:第 i 天结束第一次未持有股票的最大净利润
    • dp[i][2]:第 i 天结束第二次持有股票的最大净利润
    • dp[i][3]:第 i 天结束第二次未持有股票的最大净利润
  2. dp 递推公式:
    • dp[i][0] = max(dp[i-1][0], -prices[i]):如果第 i 天结束时是第一次持有股票,那么第 i-1 天可能持有股票,也可能没有经历过股票的买卖操作;
    • dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i]):如果第 i 天结束时是第一次未持有股票,那么第 i-1 天可能是第一次未持有股票,也可能是第一次持有股票后在第 i 天卖出;
    • dp[i][2] = max(dp[i-1][2], dp[i-1][1] - prices[i]):如果第 i 天结束时是第二次持有股票,那么第 i-1 天可能是第二次持有股票,也可能是第一次未持有股票后在第 i 天买入;
    • dp[i][3] = max(dp[i-1][3], dp[i-1][2] + prices[i]):如果第 i 天结束时是第二次未持有股票,那么第 i-1 天可能是第二次未持有股票,也可能是第二次持有股票后在第 i 天卖出。
  3. dp 数组的初始化:在第一天的时候,如果是未持有股票的状态,那么净利润就是 0;如果是持有股票的状态,净利润就是 -prices[0]
    • 题目没有明确否是可以在同一天内同时买卖股票,但由于题目允许长度为一的 prices,很显然同时买卖是允许的。所以第一天的第一次未持有股票可以看作是“买了再卖”,第一天的第二次未持有股票可以看作是“买了再卖,再买再卖”
  4. dp 的遍历顺序:从前向后遍历
  5. 举例推导:prices = [3,3,5,0,0,3,1,4]
3 3 5 0 0 3 1 4
0 -3 -3 -3 0 0 0 0 0
1 0 0 2 2 2 3 3 4
2 -3 -3 -3 2 2 2 2 2
3 0 0 2 2 2 5 5 6

本题的返回值依然很有趣,最自然的写法是在 dp[-1] 的四个值中取最大值。在之前的题中,已经解释过,想要受益最大,最后一天必然需要将股票卖出。然而根据定义,dp[-1][1] <= dp[-1][3],因为如果进行一次买卖就能获得最大净利润,那么总可以在最后一天进行一次(无意义)的买入+卖出。所以最终直接返回 dp[-1][3] 即可。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # dp[0] represents carrying the stock for the first time
        # dp[1] represents carrying no stock for the first time
        # dp[2] represents carrying the stock for the second time
        # dp[3] represents carrying no stock for the second time
        dp = [[0] * 4 for _ in range(len(prices))]
        dp[0] = [-prices[0], 0, -prices[0], 0]

        for i in range(1, len(prices)):
            dp[i][0] = max(dp[i-1][0], -prices[i])
            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])
                
        return dp[-1][3]

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

题目链接 | 解题思路

如果第一次做股票问题看到这道题,堪称是王炸。但是按照顺序解题的话,很明显这道题是上一题的一个变种,无非是从 n × 4 n\times4 n×4 的 dp 数组变成了 n × 2 k n\times 2k n×2k 的 dp 数组。为了解题时的状态清晰,我选择用 n × k × 2 n\times k \times 2 n×k×2 的三维数组,但本质上是一样的,无非就是在内部遍历时多加了一个遍历第 j 次(未)持有股票的遍历。
为了省略“没有进行过股票买卖”的讨论,要额外讨论一下 j=0 的情况。

本题总让我觉得跟多重背包有点类似,就是在原题的基础上进行了升维处理,但是题目的含义没有改变。

class Solution:
    def maxProfit(self, k: int, prices: List[int]) -> int:
        # dp[i][j][0] represents the max profit without holding the stock on day ifor the j-th time
        # dp[i][j][1] represents the max profit while holding the stock on day i for the j-th time
        dp = [[[0, 0] for j in range(k)] for _ in range(len(prices))]
        for j in range(k):
            dp[0][j] = [0, -prices[0]]

        # dp formula
        for i in range(1, len(prices)):
            for j in range(k):
                if j == 0:
                    dp[i][j][1] = max(dp[i-1][j][1], -prices[i])
                else:
                    dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i])
                dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i])
            
        return dp[-1][-1][0]

你可能感兴趣的:(代码随想录算法训练营一刷,算法)