当前位置: 首页 > article >正文

Leetcode 188 买卖股票的最佳时机 Ⅳ

java solution

class Solution {
    public int maxProfit(int k, int[] prices) {
        int n = prices.length;
        if(n == 1) return 0;
        if(k >= n/2) {
            return maxProfitUnlimited(prices);
        }
        //创建dp数组
        int[][] dp = new int[k + 1][n];

        for(int i = 1; i <= k; i++) {
            int maxDiff = -prices[0]; //最开始dp[0][j] = 0, 所以 maxDiff 初始值为 -prices[0]
            for(int j = 1; j < n; j++) {
                dp[i][j] = Math.max(dp[i][j - 1], prices[j] + maxDiff);
                maxDiff = Math.max(maxDiff, dp[i - 1][j] - prices[j]);
//dp[i][j] = Math.max(dp[i][j - 1], \sigma 1-m (prices[j] - prices[m] + dp[i - 1][m]))
//本来dp[i][j]是这个状态转移方程,为了优化计算,我们把prices[j]提出来,剩余部分定义为maxDiff
            }
        }

        return dp[k][n - 1];
    }

    private int maxProfitUnlimited(int[] prices) {
        int profit = 0;
        for(int i = 1; i < prices.length; i++) {
            if(prices[i] > prices[i - 1]) {
                profit += prices[i] - prices[i - 1];
            }
        }
        return profit;
    }
}

太好了!理解 maxDiff 是彻底掌握这道股票交易类 DP 题目的关键之一。下面我会从 直观解释 + 数学表达式 + 举例 + 图解思维 全方位帮你搞懂它。


🔍 1. 什么是 maxDiff

maxDiff 是一种“提前算好买入时机的优化变量”。

它表示在进行第 i 次交易之前,在第 j 天卖出时,如果我们曾经在第 m 天买入,那么最大能获得的利润是多少?

核心公式是:

maxDiff = max ⁡ 0 ≤ m < j ( d p [ i − 1 ] [ m ] − p r i c e s [ m ] ) \text{maxDiff} = \max_{0 \leq m < j} \big( dp[i-1][m] - prices[m] \big) maxDiff=0m<jmax(dp[i1][m]prices[m])


📌 翻译成人话:

maxDiff 记录的是:
“如果我在第 j 天卖出,那么我应该回头找一个【历史最划算的买入时机(第 m 天)】,
并且这个买入时机要考虑:我最多只能完成 i-1 次交易,买之前还有多少本金/利润。”


🧠 2. 为什么要这样设计?(解决了什么问题)

原始的动态规划状态转移是这样的:

d p [ i ] [ j ] = max ⁡ 0 ≤ m < j ( d p [ i − 1 ] [ m ] + p r i c e s [ j ] − p r i c e s [ m ] ) dp[i][j] = \max_{0 \leq m < j}(dp[i-1][m] + prices[j] - prices[m]) dp[i][j]=0m<jmax(dp[i1][m]+prices[j]prices[m])

我们要在第 j 天卖出,那么就要枚举所有 m < j 的买入日,非常慢,时间复杂度是 (O(k \cdot n^2))。

👉 但注意观察公式中 dp[i-1][m] + prices[j] - prices[m],我们可以把它拆成两部分:

d p [ i ] [ j ] = max ⁡ ( p r i c e s [ j ] + max ⁡ 0 ≤ m < j ( d p [ i − 1 ] [ m ] − p r i c e s [ m ] ) ) dp[i][j] = \max(prices[j] + \max_{0 \leq m < j}(dp[i-1][m] - prices[m])) dp[i][j]=max(prices[j]+0m<jmax(dp[i1][m]prices[m]))

✅ 这时我们只需要维护一个变量 maxDiff,它在 j 遍历过程中不断更新,这样就避免了一个内层循环,将复杂度降为 O ( k ⋅ n ) O(k \cdot n) O(kn)


📈 3. 举个例子说明

prices = [3, 2, 6, 5, 0, 3]
k = 2

假设现在正在处理:

  • i = 2 次交易
  • 当前是第 j = 5 天(价格 = 3)

我们要计算:

dp[2][5] = Math.max(dp[2][4], prices[5] + maxDiff)

maxDiff 就是:

max ⁡ ( d p [ 1 ] [ 0 ] − p r i c e s [ 0 ] , d p [ 1 ] [ 1 ] − p r i c e s [ 1 ] , … , d p [ 1 ] [ 4 ] − p r i c e s [ 4 ] ) \max(dp[1][0] - prices[0], dp[1][1] - prices[1], \dots, dp[1][4] - prices[4]) max(dp[1][0]prices[0],dp[1][1]prices[1],,dp[1][4]prices[4])

也就是说:

如果我现在要在第6天(index=5)卖出,那么我曾经在哪一天(m=0到4)买入最划算?

这“划算”的意思是:当时能留下最多的利润给这次交易用


🔁 4. 为什么每次都更新 maxDiff

因为:

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

我们在遍历每一天 j 时,不断尝试把这一天作为潜在买入点(用于未来某一天的卖出),这样后面的天数可以用 maxDiff 快速计算。

就像在走路时,手里一直带着一个“最佳买点”的记忆,随时准备在未来卖出的时候参考它。


🧊 小结回顾:

名称含义
maxDiff记录到当前为止,“买入”时最划算的一次历史记录(含前i-1次交易利润)
数学定义(\max_{m=0}^{j-1}(dp[i-1][m] - prices[m]))
更新方式maxDiff = Math.max(maxDiff, dp[i-1][j] - prices[j])
用途快速求:dp[i][j] = max(dp[i][j-1], prices[j] + maxDiff)
优势将 O(k·n²) 优化为 O(k·n),空间效率也更好

📌 如果你喜欢类比

可以把 maxDiff 想象成:

一位随身带计算器的股市助手,他在你每卖出一只股票时,都能立即告诉你:「我们过去所有历史里,哪一次买入最划算,加上你的本钱,可以赚最多!」

为什么k >= n/2时,可以认为是无限次交易?

这个问题问得非常好!🌟
理解了这个,就掌握了为什么 Leetcode 股票题中经常会特判 k >= n/2 的情况,并用贪心法处理。


💡 问题回顾:

我们最多只能做 k 次交易(1次交易 = 1次买 + 1次卖),但如果 k >= n/2
为什么就可以认为是“可以进行无限次交易”呢?


📌 回答核心:

✅ 因为一个交易周期(买+卖)至少需要 2 天

  • i 天买入,最早第 i+1 天卖出
  • 所以:
    • 最多一天买、一天卖,2 天完成一次交易
    • 如果有 n 天时间,最多也只能做 n/2 次交易

📈 举个例子(直观理解):

假设 prices.length = 6,也就是有 6 天:

prices = [3, 2, 6, 5, 0, 3]

那么:

  • 最多你能买入/卖出几次?
    • 最理想的情况是:每隔一天交易一次
      • 第1天买,第2天卖(第1次)
      • 第3天买,第4天卖(第2次)
      • 第5天买,第6天卖(第3次)
  • 所以最多可以进行 3 = 6 / 2 次交易

👉 也就是说,不管你 k 有多大,6 天时间最多只能完成 3 次完整的交易


🤖 所以当 k >= n/2

这时候 交易次数限制形同虚设,因为就算你允许再多交易,也没有足够的天数去执行。

你就可以把它看成是没有交易次数限制,也就是 可以无限次交易


🚀 如何解决无限次交易的问题?

直接使用贪心策略:

for (int i = 1; i < prices.length; i++) {
    if (prices[i] > prices[i - 1]) {
        profit += prices[i] - prices[i - 1]; // 涨了就买卖一把
    }
}

即:所有上涨的波段都吃掉,每段赚差价


✅ 总结表:

条件含义处理方法
k < n/2有限次交易限制有效用动态规划
k >= n/2交易次数比时间还多,限制无意义贪心,等价于无限次交易问题

📌 类比理解:

可以把“交易次数 k”想成“子弹数”,而“n 天”是“靶子数”

  • 如果你子弹比靶子还多,你当然可以每次都射击,不用担心次数
  • 所以:k >= n/2 时,就不再需要动态规划了,直接贪心扫一遍即可。

如果你还想看具体数据对比(比如动态规划 vs 贪心的输出对比),我也可以补充示例~


http://www.kler.cn/a/611068.html

相关文章:

  • 【计算机网络】DHCP工作原理
  • Postman 如何发送 XML 格式的 API 请求?
  • 【redis】主从复制:单点问题、配置详解、特点详解
  • 使用 Vite 提升前端开发体验:入门与配置指南
  • 【云馨AI-大模型】自动化部署Dify 1.1.2,无需科学上网,Linux环境轻松实现,附Docker离线安装等
  • JVM类文件结构详解
  • 六级词汇量积累(day12)
  • 使用请求调用本地部署的stable-diffusion接口
  • Selenium工作原理详解
  • 为何服务器监听异常?
  • 【Python】Tensorboard (Pytorch版)用法
  • springboot+mybatisplus
  • 【git拉取冲突解决】Please move or remove them before you merge. Aborting
  • 【Unity网络编程知识】使用Socket实现简单TCP通讯
  • Walrus 经济模型 101
  • 6.1 模拟专题:LeetCode 1576. 替换所有的问号
  • linux,防火墙,firewall,常用命令
  • 基于灵动微单片机SPIN系列的两轮车解决方案
  • java8循环解压zip文件---实现Excel文件数据追加
  • Elasticsearch 之 ElasticsearchRestTemplate 普通查询