【举一反三】力扣刷题-买卖股票的最佳时机(Python 实现)

快速通道

121. 买卖股票的最佳时机
122. 买卖股票的最佳时机 II
123. 买卖股票的最佳时机 III
188. 买卖股票的最佳时机 IV
309. 最佳买卖股票时机含冷冻期
714. 买卖股票的最佳时机含手续费

前言

该系列是非常经典的动态规划的题目,动态规划的核心是记录状态的改变。

121. 买卖股票的最佳时机

题目的大体意思就是一次的买卖机会,怎么操作才能赚到最多的钱

解题

这条题目一次买卖还是比较简单的,很容易想到贪心算法,维护股票最低价和卖出最高价即可

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # 记录买和卖的最大值
        buy, sell = -inf, 0
        for price in prices:
            buy, sell = max(buy, -price), max(sell, price + buy)
        return sell

当然我还是更推荐使用动态规划的形式,动态规划能够更细节的描述状态的变化,这样子理解更轻松。

就比如这个表达式

buy, sell = max(buy, -price), max(sell, price + buy)

buy = max(buy, -price)
sell = max(sell, price + buy)

以上两个表达式其实是由略微差别的,差别就是当天买的股票当天卖掉,而实际上当天买当天卖实际上也是不赚钱的,贪心思想这么写也是没毛病的。

后面几题就能体现动态规划思想的重要性了,动态规划适用于描述状态的变化,这里我描述每一天的最大收益,比如卖股票的时候需要前一天买的股票,我就取前一天的买股票后的资产,买卖股票只管取最大值即可,最后比如我要取第n天的最大收益,只需要取 sell[n-1] 即可。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # 动态规划,描述状态
        n = len(prices)
        buy = [0] * n
        sell = [0] * n
        buy[0] = - prices[0]
        for i in range(1, n):
            buy[i] = max(buy[i-1], -prices[i])
            sell[i] = max(buy[i - 1] + prices[i], sell[i - 1])
        return sell[n - 1]

优化算法的核心思想我觉得是去没必要操作,比如我只是要最后一天的最大收益,而不是每一天的收益,从一定意义上讲这一步操作是多余的。而结合题目的确如此,我当天的最大收益其实只跟前一天的购买后的资产有关,看代码只取到 buy[i-1] ,所以这个很好优化。就是上边的最一开始的贪心算法的写法,我更倾向于先用动态规划编写,再想办法优化。

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

该题就是计算最大收益

题解

我直接把每个盈利的都取出来叠加就行了。

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

当然也可以描述下每一天的收益过程,只不过第一题只能做一次交易,而这个不限次数,那
buy[i] = max(buy[i-1], sell[i-1] - prices[i])
区别就在此。

class Solution:
   def maxProfit(self, prices: List[int]) -> int:
       # 动态规划,描述状态
       n = len(prices)
       buy = [0] * n
       sell = [0] * n
       buy[0] = - prices[0]
       for i in range(1, n):
           # buy[i] = max(buy[i-1], -prices[i])
           buy[i] = max(sell[i - 1] - prices[i], buy[i - 1])
           sell[i] = max(buy[i - 1] + prices[i], sell[i - 1])
       return sell[n - 1]
123. 买卖股票的最佳时机 III

该题在第一题的基础上允许两次交易。

题解

不超过两次交易,很这边的关系层层相扣,较为复杂,不能够直接使用贪心思想了,需要描述状态的变化,有上一题的基础,很快就能写出。
现在需要描述两个交易过程,用4个变量分别描述买卖操作即可。正如下方这么表示,不过细心的会发现这边存在一个上面比较的同样一个情况,描述的是当前状态不是前一天的状态,之所以想这么些,如果严格按照状态表述的写法,最后需要取max(sell1,sell2) 并且前两天是不能描述第二次交易的,这里稍微用了贪心思想,最后直接取 sell2即可。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # 动态规划 同时维护第一次买卖和第二次买卖
        buy1, sell1 = inf, 0
        buy2, sell2 = -inf, 0

        for price in prices:
            buy1 = min(buy1, price)
            sell1 = max(sell1, price - buy1)
            buy2 = max(buy2, sell1 - price)
            sell2 = max(sell2, buy2 + price)
        return sell2

当然这边也是跟前一天相关,也可以跟第一题一样用O(1)的变量表示,这边就省略了。

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

该题在第一题的基础上允许k 次交易。

题解

不超过k 次交易,同样需要描述状态的变化,有上一题的基础,很快就能写出,我只需要描述k次交易即可,当然这边的k 可以优化下,比如有 n 天,那我最大只能进行 n//2 次完整的买卖交易,可以取个min()。

class Solution:
    def maxProfit(self, k: int, prices: List[int]) -> int:
        # DP

        n = len(prices)
        k= min(n, k)
        if k == 0:
            return 0
        buy = [-prices[0]] * k
        sell = [0] * k

        for price in prices:
            buy[0] = max(buy[0], 0 - price)
            sell[0] = max(sell[0], buy[0] + price)
            for i in range(1,k):
                buy[i] = max(buy[i], sell[i-1] - price)
                sell[i] = max(sell[i], buy[i] + price)
        return sell[k-1]
309. 最佳买卖股票时机含冷冻期

该题在第二题的基础上存在冷冻期。

题解

前面的题目是控制交易次数,我们就描述交易次数,这一题多了一个冷冻期,那我就描述一个冷冻期即可,我想要买入就必须在非解冻期才行,而非冷冻期就是冷冻期的后一天。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # 动态规划注重状态的转移 1、买入股票 2、卖出在冷冻期 3、卖出不在冷冻期
        n = len(prices)
        buy = [0] * n
        sellnby = [0] * n
        sellby = [0] * n
        buy[0] = - prices[0]
        for i in range(1, n):
            buy[i] = max(sellby[i-1] - prices[i], buy[i - 1])
            sellnby[i] = max(buy[i - 1] + prices[i], sellnby[i - 1])
            sellby[i] = max(sellnby[i - 1], sellby[i - 1])

        return max(sellby[n-1], sellnby[n-1])

其实该题目本身就是一天只能做一次操作,原先是我想要新股票,必须确保前一天已经卖出,只需要取前一天的最大收益sell[i-1],现在多了一天冷冻期,也就是说我只能取前天的最高收益即可。
注:这里有一个指针异常操作的细节,比如我第二天,那我取前天就是 第 -1 天,也正好是取最后一天的收益,因为最后一天本身就是0,所以不影响,不过还是要非常注意,一定要意识到这个地方。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # 动态规划注重状态的转移 1、买入股票 2、卖出在冷冻期 3、卖出不在冷冻期
        # 新思路就是买入卖出是一天,卖出买入需要两天,也就是说要买入,需要取两天前的卖出的结果
        n = len(prices)
        buy = [0] * n
        # sellnby = [0] * n
        sellby = [0] * n
        buy[0] = - prices[0]
        for i in range(1, n):
            buy[i] = max(sellby[i-2] - prices[i], buy[i - 1])
            # sellnby[i] = max(buy[i - 1] + prices[i], sellnby[i - 1])
            sellby[i] = max(buy[i - 1] + prices[i], sellby[i - 1])

        return sellby[n-1]
714. 买卖股票的最佳时机含手续费

如题意,原先是有冷静期,现在有手续费了。

题解

前面的题目是控制交易次数,控制冷冻期,这一题是交易要有手续费,有了动态规划的思想,这一题就很简单了。
卖出的时候描述一个支付手续费即可。

class Solution:
    def maxProfit(self, prices: List[int], fee: int) -> int:
        # 动态规划
        n = len(prices)
        buy = [0] * n
        sell = [0] * n
        buy[0] = - prices[0]
        for i in range(1, n):
            buy[i] = max(buy[i-1], sell[i-1] - prices[i])
            sell[i] = max(sell[i - 1], buy[i - 1] + prices[i] - fee)
        return sell[n-1]

完结
这个系列我非常喜欢,有闯关的快感。

你可能感兴趣的:(举一反三,算法刷题,买卖股票的最佳时机,动态规划,算法)