贪心算法:局部最优与证明思维

用区间调度、跳跃游戏和分发糖果理解贪心策略,重点说明为什么贪心必须证明正确性。

已发布文章计算机基础入门6 分钟阅读发布于 2026年9月1日更新于 2026年9月15日
文章目录
  1. 它从哪里来
  2. 贪心适合什么问题
  3. 区间调度
  4. 跳跃游戏
  5. 分发糖果
  6. 贪心和动态规划的区别
  7. 贪心证明方法
  8. 常见错误
  9. 工程场景
  10. 面试题
  11. 小结
知识目录数据结构与算法:从基础到工程实践23 / 77
LOCAL NOTE高亮并记笔记
0 / 1000 · 只保存在这台设备
READER NOTES贪心算法:局部最优与证明思维》的本机笔记

学“贪心算法:局部最优与证明思维”时,我走过的弯路可以概括成一句话:贪心题最让我不踏实的地方是代码通常很短,但我不知道这次选了局部最优,后面会不会把正确答案堵死。问题不在于知识点有多难,而在于我一开始只盯着结果,没有看到它在维护什么关系。下面是我重新补这块基础时留下的笔记。

贪心算法看起来最像“凭感觉做选择”,但真正可靠的贪心绝对不能只靠感觉。它的核心是:每一步选择局部最优,并且这个选择不会破坏全局最优。

所以学贪心,重点不是代码,而是证明。

贪心算法从候选、局部选择到证明的过程

它从哪里来

贪心不是一种特定代码模板。最小生成树、编码和调度问题的研究逐步形成了“每次做局部安全选择”的方法。它真正困难的部分一直是证明:为什么这个选择不会堵死全局最优,而不是写出排序加循环。

贪心适合什么问题

贪心通常适合具有这些特征的问题:

  • 可以拆成一系列选择。
  • 每一步有明确的局部最优策略。
  • 局部最优选择可以导向全局最优。
  • 做出选择后,不需要回头修改前面的选择。

典型问题:

  • 区间调度。
  • 合并区间。
  • 跳跃游戏。
  • 分发糖果。
  • Huffman 编码。
  • 最少箭射气球。
  • 买卖股票的部分变体。

区间调度

问题:给定若干会议区间,最多能安排多少个互不重叠的会议?

贪心策略:每次选择结束时间最早的会议。

int maxMeetings(int[][] intervals) {
    Arrays.sort(intervals, Comparator.comparingInt(a -> a[1]));
    int count = 0;
    int end = Integer.MIN_VALUE;
    for (int[] in : intervals) {
        if (in[0] >= end) {
            count++;
            end = in[1];
        }
    }
    return count;
}

为什么不是选择开始最早、持续时间最短?因为结束越早,留给后续会议的空间越大。这个策略可以用交换论证证明:任意最优方案中第一个会议,都可以换成结束更早的会议,而不会让答案变差。

跳跃游戏

问题:数组每个位置表示最远能跳多远,判断能否到达最后。

boolean canJump(int[] nums) {
    int farthest = 0;
    for (int i = 0; i < nums.length; i++) {
        if (i > farthest) return false;
        farthest = Math.max(farthest, i + nums[i]);
    }
    return true;
}

这里的局部状态是目前能到达的最远位置。只要当前位置没有超过 farthest,就能继续扩展可达范围。

分发糖果

问题:每个孩子有评分,相邻孩子评分高的糖果更多,求最少糖果数。

这题需要两次贪心:

  • 从左到右,保证右边评分高于左边时糖更多。
  • 从右到左,保证左边评分高于右边时糖更多。
int candy(int[] ratings) {
    int n = ratings.length;
    int[] candies = new int[n];
    Arrays.fill(candies, 1);
    for (int i = 1; i < n; i++) {
        if (ratings[i] > ratings[i - 1]) {
            candies[i] = candies[i - 1] + 1;
        }
    }
    for (int i = n - 2; i >= 0; i--) {
        if (ratings[i] > ratings[i + 1]) {
            candies[i] = Math.max(candies[i], candies[i + 1] + 1);
        }
    }
    int sum = 0;
    for (int c : candies) sum += c;
    return sum;
}

这题很适合说明:贪心不一定只扫一遍,有时要分别满足多个局部约束。

贪心和动态规划的区别

动态规划会保留多个状态,比较多个选择;贪心只保留当前看起来最好的选择。

如果一个问题每一步的最优选择不会影响未来最优性,可以贪心。否则就需要 DP 或搜索。

比如背包问题:

  • 0-1 背包不能简单按价值、重量或性价比贪心,因为局部选择可能导致全局不优。
  • 分数背包可以贪心,因为物品可以拆分,按性价比拿一定最优。

贪心证明方法

常见证明思路:

  1. 交换论证:把最优解中的某个选择换成贪心选择,不会变差。
  2. 反证法:假设贪心选择不在最优解中,推出矛盾。
  3. 保持不变量:每一步后都保持某个最优性质。
  4. 归纳法:证明第一步正确,后续子问题同样成立。

写贪心题解时,至少要说清楚为什么这个局部策略不会挡住未来。

常见错误

  • 样例能过就以为贪心正确。
  • 没有证明局部最优能推出全局最优。
  • 排序维度选错。
  • 区间开闭边界处理错误。
  • 把需要 DP 的问题硬贪心。
  • 遇到多个约束时只满足其中一个。

工程场景

贪心在工程里常用于调度和资源分配:

  • 缓存淘汰策略中的局部选择。
  • 任务调度优先级。
  • 区间合并和冲突检测。
  • 带宽、机器、预算的近似分配。
  • 编码压缩中的 Huffman 树。

工程中的贪心常常不是求严格最优,而是在复杂约束下求足够好、足够快的方案。此时要明确这是近似策略,而不是数学意义上的最优算法。

面试题

  1. 什么样的问题适合贪心?
  2. 贪心和动态规划有什么区别?
  3. 区间调度为什么选择结束时间最早?
  4. 为什么 0-1 背包不能简单按性价比贪心?
  5. 如何证明一个贪心策略正确?

小结

贪心最迷人的地方是简单,最危险的地方也是简单。真正可靠的贪心必须能证明。以后看到“每一步选当前最优”时,先不要急着写代码,先问:这个选择会不会让未来变差?

JARVIS · 当前文章

有哪里没看懂?可以只问这篇。

Jarvis 会限定在《贪心算法:局部最优与证明思维》及其公开关联内容中检索,并把引用定位回原文章节。

JARVIS / ARTICLE针对《贪心算法:局部最优与证明思维》提问
当前范围贪心算法:局部最优与证明思维不会悄悄扩大到全站
0 / 1000

准备好了。当前只会围绕贪心算法:局部最优与证明思维回答。

READER SIGNAL

这篇内容对你有帮助吗?

不需要登录。你的反馈会直接进入作者待处理列表。