股票交易系列 -- 动规

股票交易系列
leetcode相关题目链接:
121. 买卖股票的最佳时机
122. 买卖股票的最佳时机 II
123. 买卖股票的最佳时机 III
188. 买卖股票的最佳时机 IV
309. 买卖股票的最佳时机含冷冻期
714. 买卖股票的最佳时机含手续费

买卖股票终极问题:
买卖股票的最佳时机含手续费、冷冻期和交易次数的限制!!

买卖股票问题
三个状态,分别是:天数、允许交易的最大次数、当前的持有状态【可以用1表示持有,用0表示未持有】
dp[i][k][0 or 1]
0 <= i <= n-1, n为天数
1 <= k <= K , 大K为交易次数上限
此类问题共 n × K × 2 种状态,全部穷举就能搞定。

一次买入和一次卖出 计为一次完整的交易。
因为交易是从buy开始的,所以在buy的时候给 k 减小 1。若在sell的时候给 k 减小 1,在
buy的时候不改变交易次数 k 的话,会出现交易次数超出限制的错误!! 切记。

** base case **
dp[-1][..][0] = dp[..][0][0] = 0
dp[-1][..][1] = dp[..][0][1] = -inf

** 状态转移方程 **
# 分别对应 第 i-1天持有然后第 i 天 卖出、第 i-1天未持有然后第 i 天 不操作两种情况。
dp[i][k][0] = max(dp[i-1][k][1] + prices[i], dp[i-1][k][0])
# 分别对应 第 i-1天未持有然后第 i 天 买入、第 i-1天持有然后第 i 天 不操作两种情况。
dp[i][k][1] = max(dp[i-1][k-1][0] - prices[i], dp[i-1][k][1])

class MaxProfit:
    """
    买卖股票问题
    三个状态,分别是:天数、允许交易的最大次数、当前的持有状态【可以用1表示持有,用0表示未持有】
    dp[i][k][0 or 1]
    0 <= i <= n-1, n为天数
    1 <= k <= K , 大K为交易次数上限
    此类问题共 n × K × 2 种状态,全部穷举就能搞定。

    一次买入和一次卖出 计为一次完整的交易。
    因为交易是从buy开始的,所以在buy的时候给 k 减小 1。若在sell的时候给 k 减小 1,在
    buy的时候不改变交易次数 k 的话,会出现交易次数超出限制的错误!! 切记。

    ** base case **
    dp[-1][..][0] = dp[..][0][0] = 0
    dp[-1][..][1] = dp[..][0][1] = -inf

    ** 状态转移方程 **
    # 分别对应 第 i-1天持有然后第 i 天 卖出、第 i-1天未持有然后第 i 天 不操作两种情况。
    dp[i][k][0] = max(dp[i-1][k][1] + prices[i], dp[i-1][k][0])
    # 分别对应 第 i-1天未持有然后第 i 天 买入、第 i-1天持有然后第 i 天 不操作两种情况。
    dp[i][k][1] = max(dp[i-1][k-1][0] - prices[i], dp[i-1][k][1])

    """
    def solution(self, prices: List[int]) -> int:
        """
        121. 买卖股票的最佳时机
        这里相当于只交易一次, k = 1
        https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/
        :param prices:
        :return:
        """
        n = len(prices)
        dp = [[0] * 2 for _ in range(n)]

        for i in range(n):
            # base case
            if i == 0:
                dp[i][0] = 0
                dp[i][1] = -prices[i]
                continue

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

        return dp[n-1][0]

    def solution2(self, prices: List[int]) -> int:
        """
        121. 买卖股票的最佳时机【空间复杂度优化】
        这里相当于只交易一次, k = 1
        https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/
        :param prices:
        :return:
        """
        n = len(prices)

        # base case
        dp_i_0 = 0
        dp_i_1 = float('-inf')

        for i in range(n):
            dp_i_0 = max(dp_i_1 + prices[i], dp_i_0)
            dp_i_1 = max(-prices[i], dp_i_1)

        return dp_i_0

    def solution3(self, prices: List[int]) -> int:
        """
        122. 买卖股票的最佳时机 II
        https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/

        这里相当于可交易无穷多次, k = inf,那就可以认为k和k-1是一样的。
        dp[i][k][0] = max(dp[i-1][k][1] + prices[i], dp[i-1][k][0])
        dp[i][k][1] = max(dp[i-1][k][0] - prices[i], dp[i-1][k][1])

        发现dp数组中的k已经不会改变了,不需要记录k这个状态了,状态转移方程为:
        dp[i][0] = max(dp[i-1][1] + prices[i], dp[i-1][0])
        dp[i][1] = max(dp[i-1][0] - prices[i], dp[i-1][1])

        :param prices:
        :return:
        """
        n = len(prices)
        dp = [[0] * 2 for _ in range(n)]

        for i in range(n):
            # base case
            if i == 0:
                dp[i][0] = 0
                dp[i][1] = -prices[i]
                continue

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

        return dp[n - 1][0]

    def solution4(self, prices: List[int]) -> int:
        """
        122. 买卖股票的最佳时机 II 【空间复杂度优化】
        https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/
        """

        n = len(prices)

        # base case
        dp_i_0 = 0
        dp_i_1 = float('-inf')

        for i in range(n):
            dp_i_0 = max(dp_i_1 + prices[i], dp_i_0)
            dp_i_1 = max(dp_i_0 - prices[i], dp_i_1)

        return dp_i_0

    def solution5(self, prices: List[int]) -> int:
        """
        309. 买卖股票的最佳时机含冷冻期
        https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/
        :param prices:
        :return:
        """
        n = len(prices)
        dp = [[0] * 2 for _ in range(n)]

        for i in range(n):
            # base case 1
            if i == 0:
                dp[i][0] = 0
                dp[i][1] = -prices[i]
                continue

            # base case 2
            if i == 1:
                dp[i][0] = max(dp[0][1] + prices[i], dp[0][0])
                dp[i][1] = max(-prices[i], dp[0][1])
                continue

            dp[i][0] = max(dp[i - 1][1] + prices[i], dp[i - 1][0])
            # 第 i 天选择 buy 的时候,要从 i-2 的状态转移,⽽不是 i-1 。
            dp[i][1] = max(dp[i - 2][0] - prices[i], dp[i - 1][1])

        return dp[n - 1][0]

    def solution6(self, prices: List[int]) -> int:
        """
        309. 买卖股票的最佳时机含冷冻期 【空间复杂度优化】
        https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/
        :param prices:
        :return:
        """
        n = len(prices)

        # base case
        dp_i_0 = 0
        dp_i_1 = float('-inf')
        dp_pre_0 = 0  # 表示 dp[i - 2][0]

        for i in range(n):
            temp = dp_i_0
            dp_i_0 = max(dp_i_1 + prices[i], dp_i_0)
            dp_i_1 = max(dp_pre_0 - prices[i], dp_i_1)
            dp_pre_0 = temp

        return dp_i_0

    def solution7(self, prices: List[int], fee: int) -> int:
        """
        714. 买卖股票的最佳时机含手续费
        https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/
        :param prices:
        :return:
        """
        n = len(prices)
        dp = [[0] * 2 for _ in range(n)]

        for i in range(n):
            # base case 1
            if i == 0:
                dp[i][0] = 0
                dp[i][1] = -prices[i] - fee
                continue

            dp[i][0] = max(dp[i - 1][1] + prices[i], dp[i - 1][0])
            # 买入的时候增加交易费,相当于于买⼊股票的价格升⾼了。
            dp[i][1] = max(dp[i - 1][0] - prices[i] - fee, dp[i - 1][1])

        return dp[n - 1][0]

    def solution8(self, prices: List[int], fee: int) -> int:
        """
        714. 买卖股票的最佳时机含手续费 【空间复杂度优化】
        https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/
        :param prices:
        :return:
        """
        n = len(prices)

        # base case
        dp_i_0 = 0
        dp_i_1 = float('-inf')

        for i in range(n):
            dp_i_0 = max(dp_i_1 + prices[i], dp_i_0)
            dp_i_1 = max(dp_i_0 - prices[i] - fee, dp_i_1)

        return dp_i_0

    def solution9(self, prices: List[int]) -> int:
        """
        123. 买卖股票的最佳时机 III
        最多可以完成 两笔 交易。
        https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/        :param prices:
        :return:
        """
        n = len(prices)
        max_k = 2
        dp = [[[0] * 2 for _ in range(max_k+1)] for _ in range(n)]

        for i in range(n):
            for k in range(max_k, 0, -1):
                # base case
                if i == 0:
                    dp[i][k][0] = 0
                    dp[i][k][1] = -prices[i]
                    continue

                dp[i][k][0] = max(dp[i-1][k][1] + prices[i], dp[i-1][k][0])
                # 买入股票,交易次数变更
                dp[i][k][1] = max(dp[i-1][k-1][0] - prices[i], dp[i-1][k][1])

        return dp[n-1][max_k][0]

    def solution10(self, prices: List[int]) -> int:
        """
        123. 买卖股票的最佳时机 III
        最多可以完成 两笔 交易。
        https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/        :param prices:
        :return:
        """
        dp_i10, dp_i20 = 0, 0
        dp_i11, dp_i21 = float('-inf'), float('-inf')

        for price in prices:
            dp_i11 = max(dp_i11, -price)
            dp_i10 = max(dp_i10, dp_i11 + price)
            dp_i21 = max(dp_i21, dp_i10 - price)
            dp_i20 = max(dp_i20, dp_i21 + price)

        return dp_i20

    def solution11(self, k: int, prices: List[int]) -> int:
        """
        188. 买卖股票的最佳时机 IV
        最多可以完成 k笔 交易。
        https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/        :return:
        """
        n = len(prices)
        max_k = k
        dp = [[[0] * 2 for _ in range(max_k+1)] for _ in range(n)]

        # k=0 时的 base case
        for i in range(n):
            dp[i][0][0] = 0
            dp[i][0][1] = float('-inf')

        for i in range(n):
            for k in range(max_k, 0, -1):
                # base case
                if i == 0:
                    dp[i][k][0] = 0
                    dp[i][k][1] = -prices[i]
                    continue

                dp[i][k][0] = max(dp[i-1][k][1] + prices[i], dp[i-1][k][0])
                # 买入股票,交易次数变更
                dp[i][k][1] = max(dp[i-1][k-1][0] - prices[i], dp[i-1][k][1])

        return dp[n-1][max_k][0]

    def max_profit_all_in_one(self, max_k, prices, cooldown, fee):
        """
        终极股票交易问题【同时考虑交易次数的限制、冷冻期和⼿续费】
        输⼊股票价格数组 prices,你最多进⾏ max_k 次交易,每次交易需要额外消耗 fee 的⼿续费,
        ⽽且每次交易之后需要经过 cooldown 天的冷冻期才能进⾏下⼀次交易,请你计算并返回可以获得的最⼤利润。
        :param max_k:
        :param prices:
        :param cooldown:
        :param fee:
        :return:
        """
        n = len(prices)
        if n <= 0:
            return 0
        if max_k > n // 2:
            return self.max_profit_k_inf(prices, cooldown, fee)
        dp = [[[0] * 2 for _ in range(max_k + 1)] for _ in range(n)]
        # k=0 时的base case
        for i in range(n):
            dp[i][0][1] = float('-inf')
            dp[i][0][0] = 0
        for i in range(n):
            for k in range(max_k, 0, -1):
                # base case
                if i - 1 == -1:
                    dp[i][k][0] = 0
                    dp[i][k][1] = -prices[i] - fee
                    continue
                if i - cooldown - 1 < 0:
                    dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i])
                    dp[i][k][1] = max(dp[i - 1][k][1], -prices[i] - fee)
                    continue

                dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i])
                dp[i][k][1] = max(dp[i - 1][k][1], dp[i - cooldown - 1][k - 1][0] - prices[i] - fee)

        return dp[n - 1][max_k][0]

    def max_profit_k_inf(self, prices, cooldown, fee):
        """
         k ⽆限制,包含⼿续费和冷冻期
        :param prices:
        :param cooldown:
        :param fee:
        :return:
        """
        n = len(prices)
        dp = [[0] * 2 for _ in range(n)]
        for i in range(n):
            if i - 1 == -1:
                dp[i][0] = 0
                dp[i][1] = -prices[i] - fee
                continue
            if i - cooldown - 1 < 0:
                dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
                dp[i][1] = max(dp[i - 1][1], -prices[i] - fee)
                continue
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i])
            dp[i][1] = max(dp[i - 1][1], dp[i - cooldown - 1][0] - prices[i] - fee)
        return dp[n - 1][0]
        

你可能感兴趣的:(LeetCode,数据结构与算法,动态规划)