💥题目:452. 用最少数量的箭引爆气球 - 力扣(LeetCode)
🎯目标:给定一堆气球区间
points = [[x1, y1], [x2, y2], ...]
,每支箭垂直发射,穿越 x 轴,求最少几支箭能引爆全部气球。
🐣 第一招:暴力开干,能炸一个是一个
小哆啦最初的思路是"能合就合",于是他选择了从头到尾扫描所有区间,遇到能重叠的就合起来。
ini
function findMinArrowShots(points: number[][]): number {
if (points.length === 0) return 0;
points.sort((a, b) => a[0] - b[0]); // 先按起点排序
const merged: number[][] = [];
for (let i = 0; i < points.length; i++) {
let [start, end] = points[i];
// 尝试与merged中已有的区间合并
let mergedFlag = false;
for (let j = 0; j < merged.length; j++) {
let [mStart, mEnd] = merged[j];
if (start <= mEnd) { // 有重叠
merged[j][0] = Math.max(mStart, start);
merged[j][1] = Math.min(mEnd, end); // 缩小重叠区
mergedFlag = true;
break;
}
}
if (!mergedFlag) {
merged.push([start, end]);
}
}
return merged.length;
}
🪦 问题 :暴力方法虽然思路直白,但每次都去线性查找可以合并的区间,最坏时间复杂度是 O(n^2)
。一旦数据量大,性能"炸裂",不是炸气球,是炸程序了......
🔁 第二招:使用 reduce 合并区间
小哆啦忽然想起前几天刚学的
reduce
,想来练练手,于是有了👇
ini
function findMinArrowShots(points: number[][]): number {
if (points.length === 0) return 0;
points.sort((a, b) => a[0] - b[0]); // 按起点排序
const merged = points.reduce((acc: number[][], cur: number[]) => {
const last = acc[acc.length - 1];
if (last && cur[0] <= last[1]) {
// 有重叠,取最小交集
acc[acc.length - 1] = [last[0], Math.min(last[1], cur[1])];
} else {
acc.push(cur);
}
return acc;
}, []);
return merged.length;
}
🧠 思路核心:
- 排序后,所有气球按起点递增排列。
- 遇到区间重叠(
cur[0] <= last[1]
),说明可以一箭双雕,我们只保留交集[start, min(end1, end2)]
。 - 统计多少个独立的交集区间,就是箭的数量。
💡 优点 :
结构清晰,代码优雅,掌握了 reduce
的你可以熟练操作集合合并类问题。
⛔️ 缺点 :
虽然没超时,但它并不是最优的贪心策略 ,因为合并的是"最小交集",但没有利用"最早结束"的贪心性质。
🎯 第三招(推荐):经典贪心 · 选最早结束的射爆区间
ini
function findMinArrowShots(points: number[][]): number {
if (points.length === 0) return 0;
// 按右端点升序排序
points.sort((a, b) => a[1] - b[1]);
let arrows = 1;
let end = points[0][1];
for (let i = 1; i < points.length; i++) {
const [start, currEnd] = points[i];
if (start > end) {
arrows++; // 当前气球不在上次箭的覆盖范围
end = currEnd; // 发一支新箭,目标是当前气球的右边界
}
// 否则:被当前这支箭顺带炸掉了,什么都不用干
}
return arrows;
}
🧠 贪心的关键点在这里:
"只要当前气球的起点大于上一支箭能射到的范围,那就必须射新箭;否则,上一支箭还能炸到这个气球。"
🎯 为什么排序右端点是关键?
- 排序后,每次选择"最早结束"的气球优先射,这样能让当前这支箭尽可能炸到后续更多气球。
- 这是典型的**"区间选点贪心"模型**,思路类似"会议安排问题"。
🧪 三种方案对比分析表
比较项 | 暴力合并 | reduce合并 | 贪心策略(推荐) |
---|---|---|---|
时间复杂度 | O(n^2) |
O(n log n) |
O(n log n) |
算法思想 | 逐个比较合并 | 合并重叠取交集 | 尽可能让一箭炸更多 |
可读性 | 中 | 强 | 强 |
是否使用贪心 | ❌ | ❌ | ✅ |
算法效率 | ❌ 慢 | ✅ 稳定 | ✅✅ 最优 |
可拓展性 | 中 | 中 | 高 |
✅ 总结一下,小哆啦的最终选择:
"reduce 虽然帅,但炸气球还是要靠贪心!"
- 🌪 当你想写优雅代码练手,
reduce
合并思路非常棒; - 🧠 当你想追求最优性能,贪心策略才是射箭的"王道"。