原题
动态规划困难1 种解法

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

最多完成 k 笔交易且同一时间只能持有一股,求最大利润。

#数组#动态规划#状态机#股票

原题

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 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 。

提示:

  • 1 <= k <= 100
  • 1 <= prices.length <= 1000
  • 0 <= prices[i] <= 1000

查看原题

解题主线

  1. 用已完成的卖出次数刻画交易次数:cash[t] 表示恰好完成 t 笔后不持股,hold[t] 表示恰好完成 t 笔后仍持股。
  2. 买入不会增加已完成交易数,卖出会使次数从 t - 1 增加到 t。
  3. 一笔完整交易至少需要买入和卖出两个时点,有效交易上限可截断为 min(k, n / 2)。

解法 1:按完成交易数动态规划

严格区分恰好完成 t 笔交易的持股与不持股状态,每天从上一天的数组生成新状态,最后在至多 k 个不持股状态中取最大值。

  • 时间复杂度: O(n × min(k, n / 2))

  • 空间复杂度: O(min(k, n / 2))

JAVA
import java.util.Arrays;

final class Solution {
    private static final int UNREACHABLE = Integer.MIN_VALUE;

    public int maxProfit(int k, int[] prices) {
        if (k <= 0 || prices == null || prices.length < 2) {
            return 0;
        }

        // 有效交易数不会超过 n / 2,截断可避免维护永远不可达的状态。
        int transactions = Math.min(k, prices.length / 2);
        int[] cash = new int[transactions + 1];
        int[] hold = new int[transactions + 1];
        // cash[t]/hold[t] 表示恰好完成 t 笔交易后的不持股/持股状态。
        Arrays.fill(cash, UNREACHABLE);
        Arrays.fill(hold, UNREACHABLE);
        cash[0] = 0;
        hold[0] = -prices[0];

        for (int day = 1; day < prices.length; day++) {
            int price = prices[day];
            // 从前一天快照转移,禁止同一天更新出的状态再次参与计算。
            int[] nextCash = cash.clone();
            int[] nextHold = hold.clone();

            for (int completed = 0; completed <= transactions; completed++) {
                if (cash[completed] != UNREACHABLE) {
                    nextHold[completed] = Math.max(
                        nextHold[completed], cash[completed] - price
                    );
                }
                // 卖出才完成一笔交易,因此从 hold[completed - 1] 转入。
                if (completed > 0 && hold[completed - 1] != UNREACHABLE) {
                    nextCash[completed] = Math.max(
                        nextCash[completed], hold[completed - 1] + price
                    );
                }
            }
            cash = nextCash;
            hold = nextHold;
        }

        // 题目限制“至多”k 笔,答案应覆盖所有可达的完成次数。
        int answer = 0;
        for (int completed = 0; completed <= transactions; completed++) {
            answer = Math.max(answer, cash[completed]);
        }
        return answer;
    }
}

实现提示

  • 从上一天克隆再转移,避免原地更新把同一天的新状态误当成前一天状态。

边界与易错点

  • 旧源码把每个交易次数的首日持股状态都初始化为 -prices[0],与“恰好完成 t 笔”的注释不一致;整理后用不可达哨兵严格初始化。
  • 不可达状态不能默认填 0,否则会凭空获得已经完成多笔交易的现金。
  • 答案要取所有 cash[t] 的最大值,因为题目要求至多 k 笔,而不是必须完成 k 笔。

整理来源

由旧仓库源码复核、去重并整理;展示代码已按 Java 21 语义修正明显问题。

  • leetcode/src/main/java/greedy/Q188_maxStockProfit_IV.java