[Leetcode] 买卖股票合集(动态规划)

写完这套题,再搞一台时光机,财务自由不是梦(Doge)

==================================

相关题目链接

121 买卖股票的最佳时机

122 买卖股票的最佳时机 II

123 买卖股票的最佳时机 III

188 买卖股票的最佳时机 IV

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

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

    • 买卖股票的最佳时机(仅一次交易)

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。设计一个算法来计算你所能获取的最大利润。如果你不能获取任何利润,返回 0 。

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

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

分析

遍历每一天。用变量 minprice 记录已遍历天数的最小股价,初始值为第一天股价;

同时记录每天可以得到的最大利润 maxprofit,即前一天的最大利润 和(今日股价 - 历史最小值 minprice)的较大者,初始值为0。返回最后一天的maxprofit。

说明:下述动态规划只保存了全区间极值,没有用列表保存每天的极值

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        minprice = prices[0]
        maxprofit = 0

        for price in prices:
            minprice = min(minprice,price)
            maxprofit = max(maxprofit,price-minprice)
        
        return maxprofit

2. 买卖股票的最佳时机(可多次交易)

给你一个整数数组 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 。

方法一:所有上坡阶段求和

标志 flag 记录是否持有股票;

buy 和 sale 记录当前持有股票的交易价格,如果当前不持有股票则为0

遍历每日股价,每次买入股票后,更新buy、sale(持续更新)和flag ;

每次卖出股票后,更新buy、sale 和 flag(均为0);

最后一天需要检查 是否持有股票,持有需卖出

flag

ACTION

flag 更新

buy更新

sale更新

prices[i+1] > prices[i]

0

买入

1

prices[i]

prices[i+1]

prices[i+1] <= prices[i]

1

卖出

0

0

0

prices[i+1] > prices[i]

1

prices[i+1]

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # flag 标记是否持有股票
        flag = 0
        # buy 记录当前持有股票的买入价格
        buy = 0
        # sale 记录当前股票价格,作为暂时的卖出价格
        sale = 0
        sum = 0
        for i in range(len(prices) - 1):
            #如果当前不持有股票,次日股价比今日高,买入股票(更新买入价格),记录次日股价,更新flag标志 持有
            if prices[i+1] > prices[i] and not flag:
                buy = prices[i]
                sale = prices[i+1]
                flag = 1
            #持有股票 且 次日股价不高于今日股价 --> 卖出股票,清空股票买卖价格,更新flag标志 不持有
            elif prices[i+1] <= prices[i] and flag:
                sum = sum + (sale - buy)
                buy = 0
                sale = 0
                flag = 0 
            #持有股票 且 次日股价高于今日股价 --> 继续持有股票,更新卖出价格    
            elif prices[i+1] > prices[i] and flag:
                sale = prices[i+1]
        
        if flag == 1:
            sum = sum + (sale - buy)
        return sum

方法二:贪心

每天都买卖股票(连续持有可以看作当天卖出后再买入),卖出如果收益为正就加入总和

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

方法三:动态规划

每天有两种状态,收盘后持有或不持有股票;

初始化一个 n x 2的二维数组 dp,求第 0~ n-1 天 持有 和 不持有股票的最大收入;

第n-1 天如果持有股票,卖出一定比继续持有收益大,所以最后返回第n-1天不持有股票的最大收入

dp[i][0] 代表第i天收盘后不持有股票的最大收益 --> 前日不持有今日无操作、前日持有今日卖出

dp[i][1] 代表第i天收盘后持有股票的最大收益 --> 前日已持有今日无操作 、前日不持有今日买入

初始状态,第一天不持有股票最大收益为0;持有即买入一股,最大收益为 -prices[0]

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        dp= [[0,0]] * n
        dp[0][0] = 0
        dp[0][1] = -prices[0]

        for i in range(1,n):
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
            dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
        return dp[n-1][0]

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

给定一个整数数组 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

使用动态规划,和上述题2 一样,多了一个手续费,买入时算在开销里

class Solution:
    def maxProfit(self, prices: List[int], fee: int) -> int:
        n = len(prices)
        dp= [[0,0]] * n
        dp[0][0] = 0
        dp[0][1] = -prices[0]-fee

        for i in range(1,n):
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
            dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]-fee)
        return dp[n-1][0]

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

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

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

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

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

示例 2:
输入: prices = [1]
输出: 0

动态规划分析

每天有三种状态,收盘后股票持有、不持有(在冷冻期)、不持有(不在冷冻期)

初始化一个 n x 3的二维数组 dp,求第 0~ n-1 天 上述三种状态的最大收入;

说明:如果第i天有卖出,则收盘后进入冷冻期,第i+1天不能买入,我们把这个冷冻期的状态记在第i天

dp[i][0] 代表第i天收盘后不持有股票、且不在冷冻期的最大收益

收盘后不在冷冻期 ==> 今日没有卖出股票

+收盘后不持有股票 ==> 前日就不持有股票(前日在 或 不在冷冻期)

状态转移方程:dp[i][0] = max(dp[i-1][0], dp[i-1][1])

dp[i][1] 代表第i天收盘后不持有股票、且在冷冻期的最大收益

收盘后在冷冻期 ==> 今日卖出股票

+收盘后不持有股票 ==> 昨日持有股票 且 今日卖出股票

状态转移方程:dp[i][1] = dp[i-1][2] + prices[i]

dp[i][2] 代表第i天收盘时持有股票的最大收益

收盘时持有股票 ==> 昨日已经持有股票 或 昨日不持有不在冷冻期、今日买入

状态转移方程:dp[i][2] = max(dp[i-1][2], dp[i-1][0] - prices[i])

初始状态:

dp[0][0] = 0 #第一天不持有股票最大收益为0

dp[0][1] = 0 #第一天不持有股票最大收益为0

dp[0][2] = -prices[0] #第一天持有即买入一股,最大收益为 -prices[0]

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        if n == 1:
            return 0
        #dp= [[0,0,0]] * n
        dp= [[0] * 3  for _ in range(n)]
       
        #dp[0][0] = 0
        #dp[0][1] = 0
        dp[0][2] = -prices[0]
        # 1+ (n-1)行
        #dp = [[0, 0, -prices[0]]] + [[0] * 3 for _ in range(n - 1)]
        for i in range(1,n):
            #不持有股票,且不在冷冻期的最大收益
            dp[i][0] = max(dp[i-1][0], dp[i-1][1])
            #不持有股票,且在冷冻期的最大收益
            dp[i][1] = dp[i-1][2] + prices[i]
            #持有股票的最大收益
            dp[i][2] = max(dp[i-1][2], dp[i-1][0] - prices[i])

        return max(dp[n-1][0],dp[n-1][1])

5. 买卖股票的最佳时机(最多可以完成两笔交易)

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

方法一:动态规划

每天有五种状态,初始化一个 n x 5的二维数组 dp,求第 0~ n-1 天下述五种状态的最大收入

dp[i][0] 收盘后不持有股票,未交易过的最大收益 --> 0

dp[i][1] 收盘后是首次持有股票的最大收益 --> 昨天同状态,或前序状态 + 今日为首次买入

dp[i][2] 收盘后不持有股票,已交易过一次的最大收益 --> 昨天同状态,或前序状态 + 今日为首次卖出

dp[i][3] 收盘后是第二次持有股票的最大收益 --> 昨天同状态,或前序状态 + 今日为二次买入

dp[i][4] 收盘后不持有股票,已交易过两次的最大收益 --> 昨天同状态,或前序状态 + 今日为二次卖出

初始状态:

dp[0][0],dp[0][2],dp[0][4] 归为第一天不持有股票的情况,最大收益为0

dp[0][1],dp[0][3] 归为第一天持有股票的情况,最大收益为 -prices[0]

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        dp = [[0]*5 for _ in range(n)]     

        #dp[i][0] 不持有股票,未交易过的最大收益
        #dp[i][1] 首次持有股票状态的最大收益
        #dp[i][2] 不持有股票,已交易过一次的最大收益
        #dp[i][3] 第二次持有股票状态的最大收益
        #dp[i][4] 不持有股票,已交易过两次的最大收益
        dp[0][1] = -prices[0]
        dp[0][3] = -prices[0]
        if n == 1:
            return 0
        for i in range(1,n):
            dp[i][0] = 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 max(dp[n-1][0], dp[n-1][2], dp[n-1][4])

方法二:遍历的同时分为两个区间动态规划(运行超时)

将prices分为两个区间,各自找到两个区间一次交易的最大值(使用上述题1的动态规划方法),相加即为当前区间分段方法的最大收益。遍历每种区间分段的最大值,但是这样提交超出了时间限制。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        maxProfit = 0
        # i 为分割点,在0~n-1范围遍历
        for i in range(n):
            maxProfit1, maxProfit2 = 0, 0
            #左区间右区间各自遍历求最大收益
            #如果i=0,只执行右区间的遍历;如果i=n-1,只执行左区间的遍历,这两种情况都对应只交易一次
            if i > 0:
                minPrice1 = 10**6
                for j in range(i):
                    minPrice1 = min(minPrice1,prices[j])
                    maxProfit1  = max(maxProfit1 ,prices[j] - minPrice1)
            if i < n-1:
                minPrice2 = 10**6
                for k in range(i,n):
                    minPrice2 = min(minPrice2,prices[k])
                    maxProfit2  = max(maxProfit2 ,prices[k] - minPrice2)
            maxProfit = max(maxProfit, maxProfit1 + maxProfit2)
        return maxProfit

方法三:从两端各自动态规划求最大收益

从prices左侧开始动态规划找每日收益最大值

从prices右侧开始动态规划找每日收益最小值(高买低卖,反过来就是低买高卖)

得到两个列表,前者减后者就是按天的最大值列表,该列表最大值即为所求

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        dp = [0] * n
        dpR = [0] * n
        if n == 1:
            return 0
        
        dp[0] = 0
        minVal = prices[0]
        for i in range(n):                     
            minVal = min(minVal, prices[i])
            dp[i] = max(dp[i-1],prices[i]-minVal)

        dpR[n-1] = 0
        maxVal = prices[-1]
        for j in list(range(1,n))[::-1]:               
            maxVal = max(maxVal,prices[j])
            dpR[j-1] = min(dpR[j],prices[j-1]-maxVal)

        maxProfit = 0
        for i in range(n):
            maxProfit = max(maxProfit,dp[i] - dpR[i])

        return maxProfit

6. 买卖股票的最佳时机(最多可以完成k笔交易)

给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 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 。

其实就是将上述题5一般化,使用动态规划

两笔交易将prices分为五个区间 --> k笔交易将prices分为2k+1个区间,每天有2k+1种状态

初始化一个 n x (2k+1) 的二维数组 dp,求第 0~ n-1 天下述2k+1种状态的最大收入

dp[i][0] 收盘后不持有股票,未交易过的最大收益 --> 0

dp[i][1] 收盘后是首次持有股票的最大收益 --> 昨天同状态,或前序状态 + 今日为首次买入

dp[i][2] 收盘后不持有股票,已交易过一次的最大收益 --> 昨天同状态,或前序状态 + 今日为首次卖出

……

dp[i][2*k-1] 收盘后是第二次持有股票的最大收益 --> 昨天同状态,或前序状态 + 今日为第k次买入

dp[i][2*k] 收盘后不持有股票,已交易过两次的最大收益 --> 昨天同状态,或前序状态 + 今日为k次卖出

状态转移方程一般化:

j为奇数,收盘后持有 --> 昨日持有 或 昨日不持有、今日买入

dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] - prices[i])

j为偶数,收盘后不持有 --> 昨日不持有 或 昨日持有、今日卖出

dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + prices[i])

初始状态:

dp[0][0],dp[0][2] ... dp[0][2*k] 归为第一天不持有股票的情况,最大收益为0

dp[0][1],dp[0][3]... dp[0][2*k-1] 归为第一天持有股票的情况,最大收益为 -prices[0]

class Solution:
    def maxProfit(self, k: int, prices: List[int]) -> int:
        n = len(prices)
        if n == 1:
            return 0

        dpLine0 = [ -prices[0] if x%2 == 1 else 0 for x in range(2*k+1) ] 

        dp = [dpLine0] + [[0]*(2*k+1) for _ in range(n-1)]
        
        #dp[i][0] 不持有股票,未交易过的最大收益
        #dp[i][1] 首次持有股票状态的最大收益
        #dp[i][2] 不持有股票,已交易过一次的最大收益
        #dp[i][3] 第二次持有股票状态的最大收益
        #dp[i][4] 不持有股票,已交易过两次的最大收益
        #...

        for i in range(1,n):
            dp[i][0] = 0 
            for j in range(1,2*k+1):
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] - prices[i]) if j%2 == 1 else max(dp[i-1][j], dp[i-1][j-1] + prices[i])

        return dp[n-1][2*k]
        #return max(dp[n-1])

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值