【LeetCode】452. 用最少数量的箭引爆气球

leetcode

 

关键思路

要引爆所有气球使用最少的箭,​​核心思路是找到气球直径的重叠区域​​。一支箭能引爆多个气球的条件是这些气球的直径区间存在重叠部分。问题转化为:​​寻找最少的点(箭的位置),使每个区间至少包含一个点​​。贪心策略如下:

  1. ​​按结束位置排序​​:将气球按结束坐标 xend 升序排序,确保优先处理结束早的气球。
  2. ​​贪心覆盖​​:从第一个气球的结束位置射箭,覆盖所有与之重叠的气球(开始位置 ≤ 箭的位置),再处理剩余气球。

为什么正确?

  • ​​结束位置排序的意义​​:结束早的气球必须优先覆盖,否则可能需额外箭。
  • ​​重叠判断​​:若气球 i 的开始位置 ≤ 当前箭的位置,则箭在 [xstart_i, xend_i] 区间内,可引爆。
  • ​​贪心选择​​:在结束位置射箭可覆盖最多重叠气球(证明:若箭在更左位置,可能漏掉结束早的气球;更右位置则浪费重叠机会)。

代码实现

func findMinArrowShots(points [][]int) int {
    if len(points) == 0 {
        return 0
    }

    // 按每个气球的结束坐标 xend 升序排序
    sort.Slice(points, func(i, j int) bool {
        return points[i][1] < points[j][1]
    })

    arrowCount := 1         // 至少需要一支箭
    prevEnd := points[0][1] // 第一支箭的位置设为第一个气球的结束位置

    for i := 1; i < len(points); i++ {
        // 当前气球的开始位置 > 上一支箭的位置,说明无重叠
        if points[i][0] > prevEnd {
            arrowCount++           // 新增一支箭
            prevEnd = points[i][1] // 箭设在当前气球的结束位置
        }
        // 否则(points[i][0] <= prevEnd):当前气球被上一支箭覆盖,无需操作
    }
    return arrowCount
}

代码解析

  1. ​​排序处理​​:sort.Slice 按 xend 升序排列气球,确保优先处理结束早的气球。
  2. ​​初始化​​:第一支箭设在第一个气球的结束位置 prevEnd
  3. ​​遍历判断​​:
    • 若当前气球开始位置 > prevEnd,说明无重叠,需新箭(arrowCount++),并更新箭的位置为该气球结束位置。
    • 否则,气球被上一支箭覆盖,跳过。
  4. ​​返回结果​​:最终 arrowCount 即最小箭数。

示例测试

func main() {
    examples := [][][]int{
        {{10, 16}, {2, 8}, {1, 6}, {7, 12}}, // 输出 2
        {{1, 2}, {3, 4}, {5, 6}, {7, 8}},    // 输出 4
        {{1, 2}, {2, 3}, {3, 4}, {4, 5}},    // 输出 2
    }
    for _, points := range examples {
        fmt.Println(findMinArrowShots(points))
    }
}

复杂度分析

  1. ​​时间复杂度​​:​​O(n log n)​​
    • 排序耗时 O(n log n),遍历 O(n),总复杂度由排序主导。
  2. ​​空间复杂度​​:​​O(1)​​
    • 仅使用常数空间(排序栈空间除外,通常 O(log n))。

关键点总结

  1. ​​排序依据​​:按 xend 排序(而非 xstart),确保结束早的气球优先覆盖。
  2. ​​重叠判断​​:箭位置 prevEnd 满足 xstart ≤ prevEnd 的气球均被覆盖。
  3. ​​贪心正确性​​:每次射箭覆盖当前最大重叠组,达到全局最优。
  4. ​​边界处理​​:空数组直接返回 0。

此算法高效利用了贪心策略,通过排序和一次遍历,以最小成本解决区间覆盖问题。

posted @ 2025-06-19 13:27  云隙之间  阅读(31)  评论(0)    收藏  举报