小哆啦解题记 · 射箭引爆气球,怎么射最省箭?

💥题目: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 合并思路非常棒;
  • 🧠 当你想追求最优性能,贪心策略才是射箭的"王道"。
相关推荐
雲墨款哥12 分钟前
算法练习-Day1-交替合并字符串
javascript·算法
fishcanf1y17 分钟前
记一次与Fibonacci斗智斗勇
算法
Mr_Xuhhh37 分钟前
QT窗口(3)-状态栏
java·c语言·开发语言·数据库·c++·qt·算法
啊这.-38 分钟前
Codeforces Round 921 (Div. 1) B. Space Harbour(线段树,2100)
算法
远望樱花兔38 分钟前
【Java】【力扣】94.二叉树的中序遍历
算法·leetcode
Tim_1043 分钟前
【算法专题训练】03、比特位计数
c++·算法
张人玉1 小时前
C#`Array`进阶
java·算法·c#
MicroTech20251 小时前
Bell不等式赋能机器学习:微算法科技MLGO一种基于量子纠缠的监督量子分类器训练算法技术
科技·算法·量子计算
success3 小时前
【爆刷力扣-链表】画图自见
算法
今晚一定早睡3 小时前
代码随想录-数组-移除元素
前端·javascript·算法