贪心
每一步做局部最优选择,并论证(或按题意信任)它能导向全局最优。代码继续用 JavaScript。
1. 贪心思想与适用条件
贪心不回溯试错:当前步选定后就不再反悔。
适用时通常具备:
- 贪心选择性质:局部最优可推出全局最优
- 最优子结构:最优解包含子问题的最优解
没有这两条就硬贪,容易错。实战里更多是:识别经典模型(区间、分配、霍夫曼那类),或「先写贪心再拿反例锤」。
常见套路:
- 排序后扫一遍
- 每次取当前最优候选(堆 / 优先队列)
- 维护一个「边界 / 余额」单调推进
2. 证明思路概览(交换论证 / 反证)
面试不必写正式证明,但要能讲清「为什么这么贪」。
交换论证
假设最优解与贪心解在某处不同 → 交换成贪心的选择后,结果不会变差 → 贪心也最优。
例:活动选择里「每次选结束最早的」——若最优解选了结束更晚的,换成结束更早的,腾出的时间只多不少,仍可行。
反证
假设贪心得不到最优 → 推出与题设或单调性矛盾。
实务
刷题时:先想「每一步唯一合理的倾向是什么」,再找反例;反例找不到且符合经典模型,再写代码。
3. 区间类问题
关键字:按端点排序,再贪心选取或合并。
活动选择 / 最多不重叠区间
每次选结束最早且与已选不冲突的区间。
// intervals: [[start, end], ...],返回最多能选多少个不重叠区间
function maxNonOverlap(intervals) {
if (intervals.length === 0) return 0
intervals.sort((a, b) => a[1] - b[1]) // 按结束时间
let count = 1
let end = intervals[0][1]
for (let i = 1; i < intervals.length; i++) {
if (intervals[i][0] >= end) {
count++
end = intervals[i][1]
}
}
return count
}
变形:「最少删除多少个区间使不重叠」= 总数 − 最多不重叠个数。
合并区间
按起点排序,能合并则扩右端。
function merge(intervals) {
intervals.sort((a, b) => a[0] - b[0])
const res = []
for (const [s, e] of intervals) {
if (!res.length || res[res.length - 1][1] < s) {
res.push([s, e])
} else {
res[res.length - 1][1] = Math.max(res[res.length - 1][1], e)
}
}
return res
}