10.4.3 (python) 动态规划专题之股票买卖 —— Best Time to Buy and Sell Stock

本文深入解析了LeetCode中关于股票买卖时机的算法题目,涵盖了从无限次交易到限定次数交易的各种情况,通过动态规划的方法详细阐述了如何计算最大利润。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这一系列涉及到LeetCode中几道关于股票stock买卖时机的题目,给出一数组,代表每天股票的价格,我们要按要求,计算你所能获取的最大利润。需要注意的是,你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

Say you have an array for which the ith element is the price of a given stock on day i.Design an algorithm to find the maximum profit.

Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).

下面的题目在此基础上有所限制,我们一个一个解析。

 

122. Best Time to Buy and Sell Stock II

You may complete as many transactions as you like (i.e., buy one and sell one share of the stock multiple times).

题目解析:

这道题目的要求是:允许任意次数的交易,这一题难度较低,其实用不上动态规划的思想,甚至也不是贪婪算法。只是抛砖引玉,先了解一下这一系列题目。

在股票价格的连续变动中,我们关注的是价格的峰值和低谷,数学上的词语是 局部最大值和局部最小值。我们希望在每一周期中,都是局部最小时买入而局部最大值卖出。

因此我们的写法就是,在一个价格之后(如第3天),当价格持续递增时,我们只需要加上相对差值即可(如B代表第5天和第4天的价格差),而不是在谷之后寻找每个峰值。当某天之后价格降低,我们只需更新买入价格,根据后续的价格变动再处理即可。

Profit Graph

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if not prices or len(prices) == 0:
            return 0
        profit = 0        
        for i in range(len(prices)-1):
            if prices[i+1] > prices[i]:
                profit += prices[i+1]-prices[i]
        return profit       

 

123. Best Time to Buy and Sell Stock III

You may complete at most two transactions.

题目解析:

步入正题,只允许交易两次的情况下,如何解决问题?

我们引入状态的概念,这种情况下,每天有5种状态:

  1. 还未开始交易;
  2. 第 1 次买入一支股票buy1;
  3. 第 1 次卖出一支股票sell1;
  4. 第 2 次买入一支股票buy2;
  5. 第 2 次卖出一支股票sell2。

因此我们要动态规划的考虑第i天这五个状态下最大利润的情况,由于是一维问题,将每个状态利润的数组压缩为一个变量即可。其中状态1就没有必要记录啦,每天的值都是0。

另外需要注意的是,买入的时候,利润为-price,表示买入。看如下代码理解状态转移关系即可,如buy1[i]是在buy1[i-1]和-prices[i]之间取最大值,buy2[i]则是在buy2[i-1]和第一次交易后的利润sell1[i]-prices[i]之间取最大值。其中sell变量初值0,因为总不能卖了之后利润为负吧,利润为负就不卖。最后sell2的值是我们的结果。

这个解法有许多需要理解的地方:假如sell1[i] 和 buy2[i] 为同一天如何?因为代码里并没有限制两交易之间要隔一天,只是通过代码顺序限制了先后关系。其实这样无妨,sell1[i] 和 buy2[i] 为同一天表面上是说当天卖了后又以同样的价格买入,实际上代表今天没有任何交易,最终结果是交易一次的最大利润罢了。

这种方法一次遍历,空间O(1),十分漂亮。理解了这道题中状态的定义和股票交易问题的特点,下面我们看更一般的题目。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if not prices:
            return 0
        sell1, sell2, buy1, buy2 = 0,0, -float('inf'), -float('inf')
        for i in range(len(prices)):
            buy1 = max(buy1, -prices[i])
            sell1 = max(sell1, buy1 + prices[i])
            buy2 = max(buy2, sell1 - prices[i])
            sell2 = max(sell2, buy2 + prices[i])
        return sell2

 

188. Best Time to Buy and Sell Stock IV

You may complete at most k transactions.

题目解析:

题目给出交易次数的限制k,我们来计算最大利润。当k=1,2时是前面的题目,这道题将这一类问题泛化。

包括上面k=2的题目在内,也有一些用二维数组甚至三维数组的解法,但是思路都是一样的,只不过我们优化了一下空间,我们利用的2*k的空间。

这道题是k=2的泛化的题目,理解了k=2的解法,相信我们只需要拓展一下思路即可,我们仍是需要记录多个状态,包括:

未交易,第 1 次买入buy1,第 1 次卖出sell1,第 2 次买入buy2,第 2 次卖出sell2,......,第 k 次买入buyk,第 k 次卖出sellk;

记录每个状态下的最大利润,最后sellk的值是最终结果。

我们看如下代码,先判断数组长度和k的关系,再初始化dp变量,我们采用二维比较清晰,第一维是交易次数,第二维是买或卖的利润,最后仿照上题框架,一次遍历求结果。

class Solution:
    def maxProfit(self, k: int, prices: List[int]) -> int:
        if not prices or k == 0:
            return 0
        n = len(prices)
        #if 2*k >= n  意味着交易不受限制,参考上上题
        if 2*k >= n:
            res = 0
            for i in range(1, n): 
                res += max(prices[i] - prices[i-1], 0)
            return res

		#dp 数组定义及初始化 
        dp = [[-prices[0], 0] for _ in range(k)]   #[buy_1 sell_1] [buy_2 sell_2] ==> [buy_k sell_k] (k times)
        
        for i in range(1, n):
            for j in range(1, k+1):
                if j == 1:
                    last_sell = 0
                else:
                    last_sell = dp[j-2][1]
                #for all buys
                dp[j-1][0] = max(last_sell-prices[i], dp[j-1][0])
                #for all sells
                dp[j-1][1] = max(dp[j-1][0]+prices[i], dp[j-1][1])
        
        return dp[-1][1]        

309. Best Time to Buy and Sell Stock with Cooldown

You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times) with the following restrictions.

After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day)

题目解析:

题目要求是不限制交易次数,但是卖后要歇一天。

这道题其实并没有离开这一类问题的解题框架,我们只需在状态转移上做一些改动即可。我们记录一下i-2天的dp_sell用在第i天即可。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        if not prices:
            return 0
        n = len(prices)
        dp_buy, dp_sell = -prices[0], 0
        dp_pre_sell = 0
        for i in range(1, n):            
            dp_buy = max(dp_pre_sell-prices[i], dp_buy)
            dp_pre_sell = dp_sell   # i-1天的利润,相当于i+1天的cool down前的利润
            dp_sell = max(prices[i]+dp_buy, dp_sell)
            
        return dp_sell

一口气看了四道题,希望可以学会这一类问题的思路,动态规划数组类题目就到这儿了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值