【码道初阶】国服ad两种殊途同归的贪心算法详解Leetcode452弓箭射气球问题(与Leetcode435十分相似)
用最少箭数引爆气球:贪心策略详解
引言
在解决 LeetCode 的「452. 用最少数量的箭引爆气球」问题时,我们需要在保证射爆所有气球的前提下,找到最少的弓箭数量。本文将结合具体代码,深入解析该问题的贪心解法,用两种不同的循环写法来达成目的并揭示其与经典区间问题(Leetcode 435.区间重叠问题)的异同。
一、问题描述
给定气球区间的数组 points,其中每个区间表示气球的水平直径范围。弓箭可以从任意 x 坐标垂直射出,若该坐标在气球直径范围内,则气球被引爆。求引爆所有气球所需的最小弓箭数。
示例:
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:在 x=6 处射箭引爆 [2,8] 和 [1,6],在 x=11 处射箭引爆 [10,16] 和 [7,12]
二、算法核心思想
1. 问题分析
- 目标:找到一组射箭位置,使得每个气球至少被一支箭覆盖。
- 关键约束:弓箭可以无限前进,但必须垂直射出。
- 等价转化:问题可转化为寻找一组点,使得每个区间至少包含一个点,求最小点数。
2. 贪心策略
- 排序策略:排序在贪心算法中属于重中之重,良好的排序方法往往大大节省开销,使代码更加清晰,本题按区间右端点升序排序是最好的办法。
- 射箭规则:每次选择当前区间的右端点作为射箭位置,尽可能覆盖后续更多区间。
三、代码实现与解析
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points) {
if (points.empty()) return 0;
// 按区间右端点升序排序
sort(points.begin(), points.end(), [](const vector<int>& a, const vector<int>& b) {
return a[1] < b[1];
});
int arrows = 0;
long last_end = LONG_MIN; // 记录上一支箭的位置
for (int i = 0; i < points.size(); ++i) {
// 当前区间左端点 > 上一箭位置,需要新箭
if (points[i][0] > last_end) {
arrows++;
last_end = points[i][1]; // 射箭位置设为当前区间右端点
}
}
return arrows;
}
};
关键代码解析
-
排序预处理
sort(points.begin(), points.end(), [](const vector<int>& a, const vector<int>& b) { return a[1] < b[1]; });- 目的:将区间按右端点升序排列,确保每次选择最靠左的右端点作为射箭位置。
- 效果:例如输入
[[10,16],[2,8],[1,6],[7,12]]排序后为[[1,6], [2,8], [7,12], [10,16]]。
-
贪心遍历
for (int i = 0; i < points.size(); ++i) { if (points[i][0] > last_end) { arrows++; last_end = points[i][1]; } }- 条件判断:若当前区间左端点
points[i][0]> 上一箭位置last_end,说明需要新箭。 - 更新位置:射箭位置设为当前区间右端点
points[i][1],以覆盖后续可能重叠的区间。
- 条件判断:若当前区间左端点
四、正确性证明
1. 贪心选择性质
- 最优性:每次选择右端点射箭,能覆盖所有与该区间重叠的后续区间。
- 反证法:假设存在更优解,其第一次射箭位置不在第一个区间的右端点,则可通过替换为右端点位置,得到相同或更优结果。
2. 覆盖性分析
- 数学归纳:设前
k个区间已被最优覆盖,第k+1个区间若与当前射箭位置无重叠,则必须新增箭,而选择其右端点可最大化覆盖后续区间。
五、与区间问题的对比
相似性
- 贪心框架:均通过排序和遍历实现。
- 核心操作:判断区间重叠关系。
差异性
| 问题 | 合并区间问题 | 气球弓箭问题 |
|---|---|---|
| 排序方式 | 按左端点升序排序 | 按右端点升序排序 |
| 目标 | 合并重叠区间 | 寻找覆盖所有区间的最少点 |
| 关键条件 | 当前左端点 <= 上一右端点 | 当前左端点 > 上一射箭位置 |
六、边界与测试
1. 边界处理
- 空输入:直接返回
0。 - 单气球:返回
1。 - 相邻区间:例如
[[1,2],[2,3]]需要 2 支箭(端点相接不算重叠)。
2. 测试用例
输入:[[1,2],[3,4],[5,6],[7,8]]
输出:4 (无重叠,需4支箭)
输入:[[1,2],[2,3],[3,4],[4,5]]
输出:2 (射在2和4处)
七、复杂度分析
- 时间复杂度:O(n log n),排序占主导。
- 空间复杂度:O(1),仅用常数空间。
八、总结
通过按右端点排序和贪心遍历,我们以 O(n log n) 的时间复杂度高效解决了问题。代码简洁且覆盖所有边界条件,体现了贪心算法“局部最优即全局最优”的核心思想。理解排序策略与射箭位置更新的逻辑,是掌握此类区间覆盖问题的关键。
附:贪心排序策略最优解分析思路
在解决「452. 用最少数量的箭引爆气球」问题时,使用按右端点升序排序的方法展现了独特的优势。不过其实作者自己写的时候也在想到底怎么把这个思路给说通,我们分析问题能分析出应该按右端点排序,大概是因为这样排我们满足从局部最优→到整体最优的贪心思想。So,以下从排序策略的核心逻辑、贪心选择的最优性、覆盖范围的效率三个方面详细分析这种方法的精妙之处:
一、排序策略的核心逻辑
1. 排序目标
将区间按右端点升序排列,例如:
原始输入:[[10,16], [2,8], [1,6], [7,12]]
排序后:[[1,6], [2,8], [7,12], [10,16]]
- 排序目的:确保每次处理的区间是当前剩余区间中右端点最小的。
2. 贪心选择
每次选择当前区间的右端点作为射箭位置:
- 覆盖能力:右端点是最早结束的区间的最右点,选择此处射箭可以覆盖所有左端点小于等于该位置的后续区间。
- 示例:在排序后的区间
[[1,6], [2,8], [7,12], [10,16]]中,第一支箭射在6,覆盖[1,6]和[2,8];第二支箭射在12,覆盖[7,12]和[10,16]。
二、贪心选择的最优性
1. 局部最优性
- 选择右端点的意义:
每次射箭覆盖尽可能多的后续区间。由于后续区间的右端点更大,它们的左端点可能更靠近右侧,选择当前右端点可以最大化覆盖潜力。 - 数学归纳证明:
假设前k个区间已被最优覆盖,处理第k+1个区间时,若其左端点超出当前箭的位置,必须新增箭。选择其右端点射箭,能覆盖所有可能重叠的后续区间。
2. 全局最优性
- 反证法:
若存在更优解,其第一次射箭位置不在第一个区间的右端点,则可以通过替换为右端点位置,得到相同或更优的结果。
三、覆盖范围的效率
1. 处理相邻区间
题目规定端点相接不算重叠(如 [1,2] 和 [2,3]):
- 按右端点排序:射在
2处无法覆盖下一个区间[2,3](左端点等于当前射箭位置),需新增箭。 - 按左端点排序:射在
2处可能覆盖后续区间,但需频繁调整射箭位置。
2. 复杂示例验证
以输入 [[1,5], [2,3], [4,7]] 为例:
- 按右端点排序:
[[2,3], [1,5], [4,7]]
射在3处覆盖[2,3]和[1,5],射在7处覆盖[4,7]→ 2支箭。 - 按左端点排序:
[[1,5], [2,3], [4,7]]
射在5处仅覆盖[1,5]和[4,7],[2,3]需要额外箭 → 2支箭。
两种排序方式结果相同,但按右端点排序的逻辑更统一,无需额外条件判断。
四、对比其他排序策略
1. 按左端点排序的劣势
- 覆盖不足:射箭位置可能选择较大的右端点,导致后续区间无法被覆盖。
- 示例:区间
[[1,10], [2,3]]
按左端点排序后,射在10处覆盖所有区间;按右端点排序后,射在3处即可覆盖所有区间。
2. 按区间长度排序的问题
- 无法保证最优:短区间的覆盖范围有限,可能增加箭数。
五、边界处理与代码实现
1. 空输入处理
if (points.empty()) return 0; // 无气球需处理
2. 大整数溢出
使用 LONG_MIN 避免 INT_MIN 的溢出风险:
long last_end = LONG_MIN; // 初始化为极小值
3. 核心遍历逻辑
for (int i = 0; i < points.size(); ++i) {
if (points[i][0] > last_end) { // 需要新箭
arrows++;
last_end = points[i][1]; // 贪心选择右端点
}
}
六、总结
按右端点升序排序的方法在本题中体现以下优势:
- 最大化覆盖:每次射箭覆盖后续最多区间。
- 逻辑统一性:无需特殊处理边界和相邻区间。
- 高效简洁:时间复杂度为 O(n log n),主要来自排序操作。
这种策略通过贪心思想的局部最优选择,确保了全局最优解的必然性,是处理区间覆盖问题的经典范式。
更多推荐


所有评论(0)