leetcode - 动态规划 - Part3

121. 买卖股票的最佳时机

题目描述

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。 
注意:你不能在买入股票前卖出股票。 

示例 1: 
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

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

Related Topics 数组 动态规划

由于最多只交易一次,我们只需要找到当前天以前的股票最低价,也就能计算出当前天的最大利润。

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

下面我们尝试利用动态规划算法进行求解。

分析思路:
动态规划也就是穷举状态。对于每一天来说,我们有以下3种选择:不交易、买入、卖出。但是这3种状态并不是自由选择的,买入需要在卖出以后(第一次买入除外),卖出需要在买入以后。另外还需要考虑交易次数的限制。所以综合起来,我们的状态有3类:天数、交易次数、持股状态。
所以我们可以定义状态数组为 dp[i][j][k],表示第 i 天最多交易 j 次且持股状态为 k{0,1} 的状态下能获得的最大利润,其中 k=0 表示不持股,k=1表示持股。

  • dp[i][j][0] 如何转移?
    若不交易,则 dp[i][j][0] = dp[i-1][j][0]
    卖出后的不持股,则 dp[i][j][0] = dp[i-1][j][1]+prices[i]
    所以 dp[i][j][0] = max( dp[i-1][j][0], dp[i-1][j][1]+prices[i])

  • dp[i][j][1] 如何转移?
    若不交易,则 dp[i][j][1] = dp[i-1][j][1]
    买入后的持股,则 dp[i][j][1] = dp[i-1][j][0]-prices[i]
    所以 dp[i][j][1] = max( dp[i-1][j][1], dp[i-1][j-1][0]-prices[i])

  • base case
    第 0 天不持股,最大利润为0
    dp[0][j][0] = 0
    第 0 天持股,最大利润为-prices[0]
    dp[0][j][1] = -prices[0]
    交易次数为 0,不持股状态下利润为0,不可能为持股状态,故设置为负无穷。
    dp[i][0][0] = 0
    dp[i][0][1] = float('-inf')

  • 输出
    即第 n-1 天最大交易次数下且不持股状态下的最大利润。

有了这套思路,对于买卖股票类问题,我们均可以套用。
回到题目,由于最多交易一次,即 j=1,此时状态转移方程为:
\left\{\begin{matrix} dp[i][1][0] = max( dp[i-1][1][0], dp[i-1][1][1]+prices[i]) \\ dp[i][1][1] = max( dp[i-1][1][1], dp[i-1][0][0]-prices[i]) \\ = max( dp[i-1][1][1], -prices[i]) \end{matrix}\right.
由于交易次数这个维度均为1,所以可以省略,最后的状态转移方程为:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        if n <= 1:
            return 0
        dp = [[0, 0] for _ in range(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], -prices[i])
        # print(dp)
        return dp[n-1][0]

由于 dp[i] 只与 dp[i-1] 有关,进行状态压缩:

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

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

题目描述

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 

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

示例 2: 
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

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

提示: 
1 <= prices.length <= 3 * 10 ^ 4 
0 <= prices[i] <= 10 ^ 4 

Related Topics 贪心算法 数组 

此题与上一题的不同之处在于交易次数无限制。还是套用我们上面的分析思路,状态转移方程为

交易次数无限制,我们也可以理解为 j=+infinity,此时 j-1j 可以认为相等,即第二维同样可以省略,所以状态转移方程可写成:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # dp[i][k] 表示第i天持股状态为k{0: 不持股, 1: 持股}的最大利润
        n = len(prices)
        if n <= 1:
            return 0
        dp = [[0, 0] for _ in range(n)]
        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])
        # print(dp)
        return dp[n-1][0]

同样进行进行状态压缩:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        # dp[i][k] 表示第i天持股状态为k{0: 不持股, 1: 持股}的最大利润
        n = len(prices)
        if n <= 1:
            return 0
        sale = 0
        buy = -prices[0]
        for i in range(1, n):
            sale = max(sale, buy+prices[i])
            buy = max(buy, sale-prices[i])
        return sale

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

题目描述

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 

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

示例 2: 
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。   
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。   
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3: 
输入: [7,6,4,3,1] 
输出: 0 
解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。 
Related Topics 数组 动态规划 

现在交易次数被限制为 k=2,我们可以列举第二维的状态:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        if n <= 1:
            return 0
        dp = [[[0, 0] for _ in range(3)] for _ in range(n)]
        # 初始化
        dp[0][0][1] = float('-inf')
        dp[0][1][1] = -prices[0]
        dp[0][2][1] = -prices[0]
        for i in range(1, n):
            # dp[i][0][0] = 0
            # dp[i][0][1] = float('-inf')
            dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1]+prices[i])
            dp[i][1][1] = max(dp[i-1][1][1], -prices[i])
            dp[i][2][0] = max(dp[i-1][2][0], dp[i-1][2][1]+prices[i])
            dp[i][2][1] = max(dp[i-1][2][1], dp[i-1][1][0]-prices[i])
        return dp[n-1][2][0]

同样进行状态压缩:

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        if n <= 1:
            return 0
        first_buy = -prices[0]
        first_sale = float('-inf')
        second_buy = float('-inf')
        second_sale = float('-inf')
        for i in range(1, n):
            first_buy = max(first_buy, -prices[i])
            first_sale = max(first_sale, first_buy+prices[i])
            second_buy = max(second_buy, first_sale-prices[i])
            second_sale = max(second_sale, second_buy+prices[i])
        return second_sale

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

题目要求最多交易次数为k,针对前面几题的分析,我们可以对k进行分段讨论。

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

题目描述

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。 

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): 
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。 

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

还是套用前面的思路

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        if n <= 1:
            return 0
        dp = [[0, 0] for _ in range(n)]
        # 初始化
        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])
            if i == 1:
                dp[i][1] = max(dp[i-1][1], -prices[i])
            else:
                dp[i][1] = max(dp[i-1][1], dp[i-2][0]-prices[i])
        return dp[n-1][0]

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

参考

  1. labuladong 的解题。(放链接一直被吞)

你可能感兴趣的:(leetcode - 动态规划 - Part3)