【算法】Best Time to Buy and Sell Stock IV 交易股票嘴角时机4

本文探讨了在给定股票价格序列和交易次数限制下,如何使用动态规划算法来确定最大可能利润。通过分析不同交易策略,提供了一种有效的方法来解决这个问题。

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

【算法】Best Time to Buy and Sell Stock IV 交易股票嘴角时机4

题目

Say you have an array for which the i-th element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete at most k transactions.

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

Example 1:

Input: [2,4,1], k = 2
Output: 2
Explanation: Buy on day 1 (price = 2) and sell on day 2 (price = 4), profit = 4-2 = 2.

Example 2:

Input: [3,2,6,5,0,3], k = 2
Output: 7
Explanation: Buy on day 2 (price = 2) and sell on day 3 (price = 6), profit = 6-2 = 4.
 Then buy on day 5 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3.

给出一组股票价格 prices 和交易次数 k,求出能获取的最大收益。
PS:持有股票时不能购买,需卖出才可再购买。

解题思路

基于 kprices 数量的关系分为两种情况:

  1. k >= prices.count / 2 时,由于一次交易需要占用两天时间,所以此时所有涨幅均可交易,将所有涨幅进行加和即可。(if (prices[i] > prices[i-1]) {res += prices[i] - prices[i-1]})
  2. k < prices.count / 2 时,则 prices.count / 2 次交易可能并未取满,可采用动态规划,mp[j][i]j 天进行 i 次交易时获得的最大收益
    • mp[j][i] 的值分为两种情况
      • j 天没有进行交易,则值与 mp[j-1][i] 相同
      • 在前 j-1 天进行了 i-1 次交易,且在前 j-1 天中某一天进行了买入操作,第 j 天以 prices[j] 的价格卖出,
    • 两种情况取较大值赋予 mp[j][i]

代码实现

Runtime: 28 ms
Memory: 20.9 MB

func maxProfit(_ k: Int, _ prices: [Int]) -> Int {
        // 过滤无法买卖的情况
        guard prices.count > 1, k > 0 else { return 0 }
        // 价格走势的天数 priceLen
        let priceLen = prices.count
        // 当交易次数 k 大于 priceLen 的一半时,则每次有涨幅的时候(if (prices[i] > prices[i-1]) )都进行交易
        if k >= (priceLen / 2) {
            var res = 0
            for i in 1..<priceLen {
                if (prices[i] > prices[i-1]) {
                    res += prices[i] - prices[i-1]
                }
            }
            // 返回结果
            return res
        }
        // 若 k 小于 priceLen 的一半,则需要筛选获利最高的 k 次交易
        // mp[j][i] 为 j 天进行 i 次交易时获得的最大收益
        var mp = [[Int]](repeating: [Int](repeating: 0, count: k + 1), count: prices.count)
        // 遍历 i 次交易
        for i in 1...k {
            // localMax 实际为 mp[i - 1][0] - prices[0],
            // 由于 mp[i - 1][0] 表示 0 天,所以数值始终未 0 ,可以简写成 localMax = -prices[0]
            // localMax 表示 j 天进行 i-1 次操作后,再以 prices[j] 买入时的最大收益标识
            var localMax = -prices[0]
            //遍历 j 天
            for j in 1..<priceLen {
                // mp[j][i] 表示 j 天进行 i 次交易时获得的最大收益,分两种计算情况
                // 1. 第 j 天没有进行交易,则值与 mp[j-1][i] 相同
                // 2. 在前 j-1 天进行了 i-1 次交易,且在前 j-1 天中某一天进行了买入操作,第 j 天以 prices[j] 的价格卖出(prices[j]+localMax)
                // 两种情况取较大值赋予 mp[j][i]
                mp[j][i] = max(mp[j-1][i], prices[j]+localMax)
                // 不断更新 localMax
                // j-1 天进行 i-1 次交易,并在第 j 天买入的收益(mp[j-1][i-1]-prices[j]),与当前的 localMax 比较取较大值
                localMax = max(localMax, mp[j-1][i-1]-prices[j])
            }
        }
        // 返回 最后一天为止,进行 k 次交易的最大收益值
        return mp[prices.count-1][k]
    }

代码地址:https://github.com/sinianshou/EGSwiftLearning

<think>好的,我现在要解决用户的问题:如何通过动态规划实现N天内M次交易的最大收益计算,并记录每天的买卖策略,同时给定初始资金、每日股价数据以及最后一天必须持有现金的约束条件。首先,我需要仔细理解用户的需求,然后结合已有的引用内容,特别是引用[3]中的动态规划状态定义,以及引用[4]中的单次交易问题,来扩展到多次交易的情况。 首先,用户提到的动态规划方法,我需要确定状态变量。根据引用[3],状态的第一维是天数,第二维是持有状态(现金或股票)。但用户的问题涉及最多M次交易,所以状态可能需要扩展到第三维,记录交易次数。因为每次买卖算一次交易,买入时增加交易次数。例如,状态可以是dp[i][k][0]或dp[i][k][1],表示第i天进行了k次交易后持有现金或股票的最大收益。 接下来,初始条件需要考虑。初始资金是现金,所以第0天没有交易时,持有现金的金额是初始资金,而持有股票的状态是不可能的,可以设为负无穷。对于交易次数k的限制,当k超过M时,状态无效。 然后是状态转移方程。对于每一天i和交易次数k,有两种状态:持有现金或股票。如果当天持有现金,可能是前一天也持有现金,或者前一天持有股票并卖出(此时交易次数k不变还是k?或者卖出才算一次交易?这里需要明确)。这里可能需要调整:买入时交易次数加1,因为买入和卖出才算一次完整的交易。例如,买入的时候增加交易次数,卖出时不增加。或者,交易次数在卖出时增加。需要明确这一点。例如,每次买入算一次交易,因为卖出必须之前有买入,所以总交易次数M限制的是买入的次数。例如,允许买入M次,每次买入后必须卖出才能再买入。这种情况下,状态转移方程中,买入时会消耗一次交易机会。 比如,假设交易次数k表示已经进行了k次买入操作。那么,当从现金状态买入股票时,k增加1,但必须k <= M。卖出时,k不增加,因为卖出只是完成之前的一次买入。 因此,状态转移可能如下: - 第i天持有现金(0)的状态可以由前一天同样持有现金,或者前一天持有股票并卖出,此时收益为前一天的股票状态收益加上当天股价减去手续费。 - 第i天持有股票(1)的状态可以由前一天持有股票,或者前一天持有现金并买入,此时需要扣除当天的股价,并增加交易次数,但必须k >=1。 不过,这可能需要更仔细的分析。例如,假设每次买入和卖出算一次交易,那么每次交易由买入和卖出组成,所以交易次数在卖出时增加。或者,可能不同的定义方式会影响状态的设计。例如,有些文献中将买入并卖出视为一次完整的交易,此时交易次数在卖出时增加。这种情况下,状态转移方程中,买入时不增加交易次数,卖出时增加。 但根据常见的动态规划处理方式,例如在"Best Time to Buy and Sell Stock IV"问题中,交易次数k通常指完成k次完整的交易(买入和卖出)。但用户的问题中,可能需要允许最多M次交易,每次交易是买入后卖出。这种情况下,状态设计可能需要将交易次数限制为M次。例如,当k表示已经完成了k次交易,那么买入的时候不算次数,卖出时算次数。但这样可能不太容易处理,因为买入和卖出必须成对出现。 另一种常见的方法是,将交易次数k定义为已经进行了k次买入操作(即最多持有k次股票)。这可能更适合状态转移的设计。例如,在每次买入时,k增加1,而卖出时不增加。因此,当允许最多M次交易时,k的范围是0到M。这种情况下,状态可以是dp[i][k][0或1],其中0表示持有现金,1表示持有股票。初始状态是dp[0][0][0] = 初始资金,其他状态初始化为负无穷。 状态转移方程可能如下: 对于每一天i,从1到N: - 对于每个交易次数k从0到M: 持有现金的状态(0): dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + price[i] - fee) 即,当天不操作,或者卖出之前持有的股票,此时交易次数k不变,因为卖出属于完成之前的一次买入交易,此时手续费可能在卖出时扣除。 持有股票的状态(1): dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - price[i]) 这里,买入时需要k-1次交易后的现金状态,因为买入会占用一次交易机会(k必须>=1),所以k必须大于等于1才能进行买入操作。此时,交易次数k指的是已经进行了k次买入操作,所以当买入时,k必须从k-1转移过来。 这样,当k=0时,无法买入,所以dp[i][0][1] = -infinity。 这样的状态转移方程是否正确?需要验证。例如,当k=1时,买入一次,之后卖出,此时交易次数是否算完成了一次?假设是的,那么在卖出时,k=1,因为此时买入次数是1,卖出并不改变k的值。这可能需要进一步调整。 不过,这样的设计可能适用于问题中的M次交易,每次交易指的是买入和卖出各一次。例如,允许最多完成M次完整的买入卖出交易。这种情况下,每次买入会占用一次交易次数,因此当k=1时,只能进行一次买入卖出。因此,当状态中的k是已经完成的交易次数,或者允许的买入次数? 这个问题可能需要参考经典的动态规划解法。例如,对于股票交易问题,常见的状态定义是:dp[i][k][0/1],其中i是天数,k是允许的最大交易次数(即最多进行k次交易),0/1表示是否持有股票。此时,买入时会减少剩余的交易次数。例如,当买入时,k减少1,因为每次交易由买入和卖出构成。例如,状态转移方程可能为: dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + price[i] - fee) dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - price[i]) 这样,k表示剩余的交易次数。当k=0时,不能再进行任何交易。而初始状态中,dp[0][k][0] = 初始资金,dp[0][k][1] = -infinity(因为第一天不可能持有股票,除非允许在第一天买入)。或者,如果允许在第一天买入,则dp[0][M][1] = 初始资金 - price[0],而dp[0][M-1][0] = 初始资金。这可能需要更仔细的处理。 不过,用户的问题中提到的是最多M次交易,而每次交易是买入和卖出一次。因此,当k是剩余的交易次数时,每完成一次完整的交易(买入并卖出)后,k减少1。这种情况下,状态转移方程可能需要这样的处理。例如: 初始化时,dp[0][M][0] = 初始资金,因为还没有进行任何交易,持有现金。而dp[0][M][1] = 初始资金 - price[0],如果允许在第一天买入的话,此时剩余的交易次数是M-1?或者可能我的理解有误。 这个部分可能需要更详细的分析。例如,当k代表剩余的交易次数时,买入操作会占用一次交易机会,因为必须卖出才能完成这次交易。所以在买入时,k减少1,此时剩余的交易次数为k-1。或者,卖出时才减少交易次数。例如: - 买入时,剩余交易次数k必须>=1,因为需要有一次交易机会。买入后,剩余次数还是k,直到卖出时才减少。但这样可能会有问题,因为买入和卖出必须成对。 可能正确的处理是,每次完整的交易(买入+卖出)算一次,所以当完成卖出时,k减少1。因此,在买入的时候,k还是原来的值,因为此时还没有完成交易。卖出时,才算完成一次交易,因此k减少1。 但这样的处理方式可能会比较复杂。例如,状态转移方程可能需要同时处理买入和卖出操作中的k的变化。这种情况下,状态设计可能需要更多的维度,或者需要重新考虑状态的定义。 例如,另一种状态定义方式是:dp[i][k][0/1],其中k表示已经完成的交易次数。此时,当进行卖出操作时,k增加1。这样,k的范围是从0到M。在状态转移时,买入的时候不改变k,卖出的时候k增加1。这可能更直观。 例如: dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + price[i] - fee) //卖出操作,此时k不变? 或者,在卖出时,k增加1。例如: dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k-1][1] + price[i] - fee) 这样,在卖出时,交易次数从k-1变为k,表示完成了一次交易。这似乎更合理,因为只有当卖出后,交易次数才会增加。此时,状态转移方程中的卖出操作会导致k的增加,而买入时k不变。 但这样的处理可能需要更多的状态转移条件,例如,在卖出时,k必须>=1,因为此时k-1 >=0。 这样的状态设计可能更合理。例如: 对于第i天,交易次数k,状态0(持有现金): - 如果前一天也是状态0,那么直接继承。 - 如果前一天是状态1(持有股票),当天卖出,则交易次数增加1,因此需要从k-1转移过来。例如,dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k-1][1] + price[i] - fee) 但是这样处理的话,当k=0时,无法卖出,因为k-1会是负数,这显然不合理。所以可能需要调整,或者这里的k代表的是已经完成的交易次数,允许的最大次数是M,那么当k超过M时,状态无效。 或者,可能我之前的状态定义有误。例如,正确的状态定义应该是dp[i][k][h],其中k是允许的最大交易次数,即还可以进行k次交易。此时,当买入并卖出一次后,k减少1。这可能更符合经典的处理方式。比如,参考LeetCode中的“Best Time to Buy and Sell Stock IV”问题。 根据该问题的标准解法,状态定义为: - dp[i][k][0]:第i天结束时,最多进行了k次交易,持有现金时的最大收益。 - dp[i][k][1]:第i天结束时,最多进行了k次交易,持有股票时的最大收益。 状态转移方程: dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i] - fee) dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) 这里,当买入时,交易次数减少1,因为每次交易由买入和卖出构成,所以买入时使用了一次交易机会。例如,如果当前允许k次交易,那么买入操作将允许的交易次数减少到k-1次,因为之后必须卖出才能完成这次交易。 初始化时,dp[0][k][0] = 0(如果初始资金是0的话,但用户问题中给定的是初始资金,可能需要调整),而dp[0][k][1] = -prices[0]。但用户的问题中的初始资金可能是一个变量,比如初始资金为C,那么初始状态可能需要调整。 例如,初始时(第0天,即第一天),如果允许k次交易,持有现金的最大收益是初始资金C,而持有股票的收益是C - price[0],但只能在k>=1的情况下,因为买入需要占用一次交易机会。因此,可能需要将状态初始化为: 对于k从0到M: - dp[0][k][0] = C (初始资金) - dp[0][k][1] = 如果k >=1,则为 C - price[0],否则为 -infinity(不可达) 这样,在第一天,如果允许至少一次交易(k >=1),则可以买入,否则不能。 接下来,对于每一天i >=1,和每个k从1到M: dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + price[i] - fee) dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - price[i]) 这样,当k=0时,无法进行任何交易,因此持有现金的收益始终为C,而持有股票的状态不可能存在,因此设为-∞。 最终,因为最后一天必须持有现金,所以最大收益为dp[N-1][M][0],即最后一天,进行了最多M次交易,持有现金的最大收益。 现在,关于记录每天的买卖策略,这需要回溯动态规划的状态转移路径。例如,在计算完所有状态后,从最终状态反向追踪,确定每一天的操作(买入、卖出或持有)。例如,对于第i天,状态k和h,查看该状态是通过哪种操作转移而来的,从而确定当天是否有买卖操作。 例如,假设最终状态是dp[N-1][M][0],那么需要找出这个状态是从哪里转移来的。如果是从前一天的dp[N-2][M][0]转移而来,则最后一天没有操作。否则,如果是从前一天的dp[N-2][M][1]卖出而来,则最后一天进行了卖出操作。然后继续追踪前一天的M次交易时的状态,直到回溯到初始状态。 这样的回溯过程需要记录每一步的转移路径,可能需要使用辅助数组或记录每一步的选择(买入、卖出、无操作)。 现在,结合用户提供的引用内容,引用[3]中的状态定义是二维的(天数,持有状态),没有考虑交易次数。而用户的问题需要扩展到三维状态,包括交易次数。引用[4]中的例子是单次交易的情况,此时k=1,可以视为特殊情况。 因此,用户的问题需要将动态规划的状态扩展到三维,即天数、交易次数、持有状态,并处理状态转移时的交易次数变化。 此外,用户的问题还提到需要记录每天的买卖策略,这需要在动态规划过程中记录每一步的操作,或者通过回溯状态转移路径来重建买卖策略。 总结步骤: 1. 状态定义:dp[i][k][h],表示第i天,最多进行了k次交易,当前持有状态h(0现金,1股票)时的最大收益。 2. 初始化:dp[0][k][0] = 初始资金,当k>=0;dp[0][k][1] = 初始资金 - price[0](如果允许买入,即k>=1),否则为负无穷。 3. 状态转移方程: - 对于持有现金状态(h=0): dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + price[i] - fee) - 对于持有股票状态(h=1): dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - price[i]) 4. 最终结果:dp[N-1][M][0] 5. 买卖策略记录:通过反向追踪状态转移路径,确定每天的买卖操作。 现在,可能遇到的问题: - 如何处理交易次数k的范围?例如,当k=0时,不能进行任何交易,只能持有现金。 - 如何初始化初始状态,特别是当初始资金不为0时? - 如何处理手续费fee?是在卖出时扣除还是在买入时扣除? 根据引用[1]中的问题,手续费在每次交易时扣除。例如,每次卖出时扣除手续费。因此,在状态转移方程中,卖出时的收益要减去fee。 现在,验证状态转移方程的正确性。例如,对于M=1的情况: 假设价格序列为[7,1,5,3,6,4],初始资金为0,fee=0。根据引用[4],最大收益是5(买入价1,卖出价6)。按照状态转移方程,当k=1时: 在第1天(price=1),可以买入,此时dp[1][1][1] = max(负无穷,dp[0][0][0] -1) = 0 -1 = -1。 接下来,在第5天(price=6),如果卖出: dp[5][1][0] = max(dp[4][1][0], dp[4][1][1] +6) 假设在第4天持有股票的最大收益是dp[4][1][1],那么如果卖出的话,收益为dp[4][1][1]+6。此时,根据状态转移方程,应该得到正确的最大收益。 但具体计算需要详细推导每个步骤,这可能比较繁琐,但大致思路正确。 接下来,关于记录买卖策略,这需要记录每一步的选择,比如在第i天是买入、卖出还是无操作。这可以通过在状态转移时记录前驱状态,或者通过回溯时比较状态值来确定。 例如,对于每个状态dp[i][k][h],记录是从哪个状态转移而来(例如,前一天的k值,前一天的h值,以及操作类型)。这样,在回溯时,可以根据这些信息重建操作路径。 例如,在计算dp[i][k][0]时,如果它来自dp[i-1][k][0],则当天无操作;如果来自dp[i-1][k][1] + price[i] - fee,则当天卖出股票。同样,对于dp[i][k][1],如果来自dp[i-1][k][1],则无操作;否则,来自dp[i-1][k-1][0] - price[i],则当天买入。 因此,在回溯时,可以从最后一天的状态出发,逆推每一天的操作。例如: 假设最终状态是dp[N-1][M][0]。查看这个状态是来自卖出还是保持: - 如果dp[N-1][M][0] == dp[N-2][M][0],则最后一天无操作。 - 否则,最后一天卖出了股票,此时对应的前一天的状态是dp[N-2][M][1],并且操作是卖出,收益增加price[N-1] - fee。 然后,继续追踪前一天的状态dp[N-2][M][1],查看它是来自保持还是买入: - 如果dp[N-2][M][1] == dp[N-3][M][1],则无操作。 - 否则,来自dp[N-3][M-1][0] - price[N-2],即在第N-2天进行了买入操作,此时交易次数减少1到M-1。 这样,可以逐步逆推出买入和卖出的日期。 需要注意的是,当交易次数较多时,可能会有多个买入和卖出点,因此需要记录所有可能的路径,但动态规划的状态转移保证了最大收益的路径。 现在,回到用户的问题,如何实现这一动态规划算法,并记录每天的买卖策略? 首先,需要构建三维数组dp,其中i的范围是0到N-1天,k的范围是0到M次交易,h是0或1。由于M可能较大,当M很大时,三维数组可能占用较多内存,但用户的问题中没有给出M的具体范围,可能需要优化空间复杂度。例如,如果M的值很大,可以考虑使用滚动数组来优化空间。但这里假设M的值较小,可以接受三维数组。 其次,在实现时,需要注意边界条件,例如当k=0时,只能持有现金,且收益为初始资金,不能进行任何交易。此外,初始化时需要处理所有可能的k值。 最后,在回溯时,需要记录每一步的操作,这可能需要一个辅助的三维数组来记录每个状态的前驱状态和操作类型。 例如,可以创建一个三维的action数组,action[i][k][h]记录在第i天,状态k和h时,进行的操作(例如,'buy'、'sell'、'hold')。 这样,在回溯时,可以根据action数组来确定每一步的操作。 现在,总结动态规划的实现步骤: 1. 初始化三维数组dp,大小为N x (M+1) x 2,因为k的范围是0到M。 2. 初始化dp[0][k][0] = 初始资金,对于所有k >=0。 dp[0][k][1] = 如果k >=1,则初始资金 - price[0],否则为负无穷。 3. 对于每一天i从1到N-1: a. 对于每个交易次数k从0到M: i. 计算dp[i][k][0]:取max(保持现金,卖出股票)。 - 保持现金:dp[i-1][k][0] - 卖出股票:如果k >=0,则dp[i-1][k][1] + price[i] - fee 所以,dp[i][k][0] = max(保持现金的值,卖出后的值) 如果卖出后的值更大,则记录操作为'sell'。 ii. 计算dp[i][k][1]:取max(保持股票,买入股票)。 - 保持股票:dp[i-1][k][1] - 买入股票:如果k >=1,则dp[i-1][k-1][0] - price[i] 所以,dp[i][k][1] = max(保持股票的值,买入后的值) 如果买入后的值更大,则记录操作为'buy'。 4. 最终的最大收益是dp[N-1][M][0]。 5. 回溯路径,从i=N-1, k=M, h=0开始,逆推每一天的操作: a. 如果当前状态是dp[i][k][0],查看是否由卖出操作得到: - 如果是,那么第i天有卖出操作,前一个状态是dp[i-1][k][1]。 然后,k保持不变,因为卖出操作属于当前交易次数k。 然后,移动到i-1, k, h=1。 b. 如果当前状态是保持现金,则无操作,前一个状态是dp[i-1][k][0]。 c. 如果当前状态是dp[i][k][1],查看是否由买入操作得到: - 如果是,那么第i天有买入操作,前一个状态是dp[i-1][k-1][0]。 然后,k减少1,移动到i-1, k-1, h=0。 这样,通过逆向追踪,可以得到所有的买入和卖出操作。 需要注意的是,在回溯过程中,必须记录每个操作对应的日期和交易次数,这可能比较复杂。 此外,用户的问题要求记录每天的买卖策略,即每一天是买入、卖出还是无操作。因此,在回溯过程中,需要确定每一天的操作,并记录下来。 例如,假设在回溯过程中发现第3天进行了买入操作,第5天进行了卖出操作,那么对应的策略就是在第3天买入,第5天卖出。 现在,关于代码实现,可能需要使用Python或其他语言编写动态规划的逻辑,并处理三维数组。由于用户的问题可能需要代码示例,因此需要提供一个伪代码或Python代码框架。 此外,用户提到最后一天必须持有现金,因此在最终状态dp[N-1][M][0]必须有效,而其他状态可能不需要考虑。 现在,针对用户的问题,给出一个分步的解决方案: 步骤1:定义状态 使用三维数组dp[i][k][h],其中: - i:0 ≤ i < N,表示第i天。 - k:0 ≤ k ≤ M,表示最多进行了k次交易。 - h:0或1,表示持有现金或股票。 步骤2:初始化 对于第0天(i=0): - dp[0][k][0] = 初始资金,对于所有k ≥0。 - dp[0][k][1] = 初始资金 - price[0](当k ≥1时),否则为负无穷(不可达)。 步骤3:状态转移 对于每个i从1到N-1: 对于每个k从0到M: 计算dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i] - fee) 计算dp[i][k][1] = max(dp[i-1][k][1], (dp[i-1][k-1][0] - prices[i]) if k ≥1 else -inf) 步骤4:获取最大收益 最大收益为dp[N-1][M][0] 步骤5:回溯买卖策略 从i=N-1, k=M, h=0开始,逆推操作路径,记录每天的买卖操作。 例如: current_i = N-1 current_k = M current_h = 0 操作列表初始化为空数组,长度N,记录每天的操作。 while current_i >=0: if current_h ==0: 如果current_i ==0: 如果current_k ==0: 操作列表[current_i] = '无操作' break else: 如果dp[current_i][current_k][0] == dp[current_i-1][current_k][0]: 操作列表[current_i] = '无操作' current_i -=1 else: 操作列表[current_i] = '卖出' current_h =1 current_i -=1 else: # current_h ==1 如果current_i ==0: 操作列表[current_i] = '买入'(因为k≥1) current_k -=1 current_h =0 else: if dp[current_i][current_k][1] == dp[current_i-1][current_k][1]: 操作列表[current_i] = '无操作' current_i -=1 else: 操作列表[current_i] = '买入' current_k -=1 current_h =0 current_i -=1 但这样可能不够准确,需要更仔细的处理,例如,在回溯时需要确定当前状态是从哪个前驱状态转移而来。因此,可能需要一个辅助的数组来记录每一步的选择,比如在计算dp时,同时记录每一步的决策。 例如,使用两个三维数组prev_k和prev_h,或者使用一个结构体来保存前驱状态的信息。但这可能比较复杂。 另一种方法是,在每次计算dp[i][k][h]时,记录该状态是由哪个操作得到的,例如,用三个数组来记录是否进行了买入或卖出操作。 例如,对于每个状态dp[i][k][h],可以记录: - 如果是持有现金(h=0),则记录是否是通过卖出操作到达的。 - 如果是持有股票(h=1),则记录是否是通过买入操作到达的。 这可能需要额外的存储空间,但可以方便回溯。 因此,在代码实现时,可以创建两个三维数组:一个记录最大收益,另一个记录操作的来源。 综上所述,用户的问题需要构建一个三维动态规划数组,处理状态转移,并回溯路径以记录每天的买卖策略。这需要较为复杂的代码实现,但思路是清晰的。 最后,根据用户提供的引用内容,特别是引用[3]中的状态定义,这里的状态扩展到了交易次数,因此可以引用该文献作为参考。同时,用户的问题中的约束条件,如最后一天必须持有现金,已经在动态规划的最终状态中得到满足,因为最终状态是dp[N-1][M][0]。</think>### 分步解答 #### 一、动态规划状态定义 定义三维数组$dp[i][k][s]$,其中: - $i$表示第$i$天(0 ≤ i < N) - $k$表示已完成$k$次交易(0 ≤ k ≤ M) - $s$表示持有状态(0=现金,1=股票) 状态转移方程为: $$ dp[i][k][0] = \max(dp[i-1][k][0],\ dp[i-1][k][1] + price[i] - fee) $$ $$ dp[i][k][1] = \max(dp[i-1][k][1],\ dp[i-1][k-1][0] - price[i]) $$ 初始化条件: $$ dp[0][0][0] = 初始资金 $$ $$ dp[0][k][1] = 初始资金 - price[0] \quad (当k≥1) $$ 其他状态初始为负无穷[^3] #### 二、核心算法实现 ```python def max_profit(prices, fee, M, initial_capital): N = len(prices) dp = [[[-float('inf')] * 2 for _ in range(M+1)] for __ in range(N)] # 初始化 for k in range(M+1): dp[0][k][0] = initial_capital if k >=1: dp[0][k][1] = initial_capital - prices[0] for i in range(1, N): for k in range(M+1): # 持有现金的状态转移 dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i] - fee) # 持有股票的状态转移 if k >=1: buy_candidate = dp[i-1][k-1][0] - prices[i] dp[i][k][1] = max(dp[i-1][k][1], buy_candidate) else: dp[i][k][1] = dp[i-1][k][1] return dp[N-1][M][0] ``` #### 三、买卖策略回溯 通过反向追踪状态转移路径: ```python def trace_strategy(prices, dp, M): i = len(prices)-1 k = M state = 0 strategy = ['hold'] * len(prices) while i >=0 and k >=0: if state ==0: if i>0 and dp[i][k][0] == dp[i-1][k][0]: strategy[i] = 'hold' i -=1 else: if i>0: strategy[i] = 'sell' state =1 i -=1 else: if k>0 and i>0 and dp[i][k][1] == dp[i-1][k-1][0] - prices[i]: strategy[i] = 'buy' k -=1 state =0 i -=1 else: strategy[i] = 'hold' i -=1 return strategy ``` #### 四、复杂度分析 - 时间复杂度:$O(N \times M)$ - 空间复杂度:$O(N \times M)$,可通过滚动数组优化至$O(M)$ #### 五、应用示例 给定参数: - 初始资金=10000 - 手续费=5 - 最大交易次数=2 - 价格序列=[20,25,18,30,22,28] 算法将输出最大收益及每日操作策略[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值