452. 用最少数量的箭引爆气球
解题思路
这道题的贪心核心是:按气球右边界排序,尽可能让一支箭射穿更多重叠气球。
- 排序:将所有气球按右边界升序排列(关键!优先处理右边界小的气球,能最大化后续重叠可能性)。
- 遍历射箭 :
- 初始化第一支箭的位置为第一个气球的右边界。
- 遍历后续气球:若当前气球的左边界 > 箭的位置,说明无法用当前箭射中,需要新增一支箭,并更新箭的位置为当前气球的右边界;否则,当前箭可射中该气球,无需操作。
- 最终箭的总数即为答案。
完整 Java 代码
java
import java.util.Arrays;
class Solution {
public int findMinArrowShots(int[][] points) {
if (points.length == 0) {
return 0;
}
// 步骤1:按气球右边界升序排序(注意用Long避免int溢出)
Arrays.sort(points, (a, b) -> {
if (a[1] > b[1]) return 1;
else if (a[1] < b[1]) return -1;
else return 0;
});
// 步骤2:初始化箭的数量和第一支箭的位置
int arrows = 1;
long arrowPos = points[0][1]; // 用long防止溢出
// 步骤3:遍历气球,判断是否需要新增箭
for (int i = 1; i < points.length; i++) {
// 当前气球的左边界 > 箭的位置,需要新增箭
if (points[i][0] > arrowPos) {
arrows++;
arrowPos = points[i][1];
}
// 否则,当前箭可射中该气球,无需操作
}
return arrows;
}
}
代码解析
- 边界处理:若气球数组为空,直接返回 0。
- 排序关键 :按右边界升序排序,这是贪心的核心 ------ 射穿右边界最小的气球,能覆盖最多后续重叠气球;注意用
Long存储箭的位置,避免 int 溢出(题目中坐标范围可能很大)。 - 遍历射箭:初始箭数为 1(至少需要一支箭),箭的位置为第一个气球的右边界;遍历后续气球,若气球左边界超过箭的位置,说明无法覆盖,新增箭并更新箭的位置为当前气球右边界。
435. 无重叠区间
解题思路
这道题的贪心核心是:按区间右边界排序,优先保留右边界小的区间,最大化剩余空间(本质是 "最少删除 = 总数 - 最多保留")。
- 排序:将区间按右边界升序排列。
- 统计最多可保留的不重叠区间数 :
- 初始化保留数为 1,上一个保留区间的右边界为第一个区间的右边界。
- 遍历后续区间:若当前区间的左边界 ≥ 上一个区间的右边界,说明不重叠,保留数 + 1,并更新上一个区间的右边界;否则跳过(该区间需删除)。
- 计算删除数:总区间数 - 最多保留数。
完整 Java 代码
java
import java.util.Arrays;
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals.length == 0) {
return 0;
}
// 步骤1:按区间右边界升序排序
Arrays.sort(intervals, (a, b) -> {
return a[1] - b[1];
});
// 步骤2:统计最多可保留的不重叠区间数
int keep = 1; // 至少保留1个区间
int lastRight = intervals[0][1]; // 上一个保留区间的右边界
for (int i = 1; i < intervals.length; i++) {
// 当前区间左边界 ≥ 上一个右边界,不重叠,保留
if (intervals[i][0] >= lastRight) {
keep++;
lastRight = intervals[i][1];
}
}
// 步骤3:需要删除的数量 = 总数 - 保留数
return intervals.length - keep;
}
}
代码解析
- 边界处理:区间数组为空时返回 0。
- 排序逻辑:按右边界升序排序,这是贪心的关键 ------ 保留右边界小的区间,能给后续区间留出更多空间,从而保留更多不重叠区间。
- 统计保留数:初始保留数为 1(第一个区间),遍历后续区间,只要不与上一个保留区间重叠,就保留并更新右边界。
- 计算删除数:总区间数减去最多保留数,即为最少需要删除的区间数。
763. 划分字母区间
解题思路
这道题看似是字符串问题,实则是重叠区间的变形 ,贪心核心是:先记录每个字母最后出现的位置,再找当前区间的最大右边界。
- 预处理:遍历字符串,记录每个字母最后一次出现的索引(相当于每个字母的 "区间右边界")。
- 划分区间 :
- 初始化当前区间的左边界
start = 0,当前区间的最大右边界end = 0。 - 遍历字符串:
- 更新
end为 "当前字母最后出现位置" 和 "当前 end" 的最大值(扩展当前区间的右边界)。 - 若遍历到的索引 ==
end,说明当前区间的所有字母都只在该区间内出现,记录区间长度(end - start + 1),并更新start = end + 1开始下一个区间。
- 更新
- 初始化当前区间的左边界
- 最终返回所有区间长度的列表。
完整 Java 代码
java
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<Integer> partitionLabels(String s) {
List<Integer> result = new ArrayList<>();
// 步骤1:记录每个字母最后出现的索引(ASCII码映射)
int[] lastIndex = new int[26];
for (int i = 0; i < s.length(); i++) {
lastIndex[s.charAt(i) - 'a'] = i;
}
// 步骤2:划分区间
int start = 0; // 当前区间左边界
int end = 0; // 当前区间最大右边界
for (int i = 0; i < s.length(); i++) {
// 更新当前区间的最大右边界
end = Math.max(end, lastIndex[s.charAt(i) - 'a']);
// 遍历到区间右边界,记录长度
if (i == end) {
result.add(end - start + 1);
start = end + 1; // 重置左边界,开始下一个区间
}
}
return result;
}
}
代码解析
- 预处理最后索引:用长度为 26 的数组(对应 26 个小写字母)记录每个字母最后出现的索引,时间复杂度 O (n)。
- 划分区间核心 :
start标记当前区间的起始位置,end标记当前区间的最大右边界(确保区间内所有字母的最后出现位置都在end内)。- 遍历到
i == end时,说明当前区间内的所有字母都不会出现在后续位置,此时划分区间并记录长度。
- 结果返回:将所有区间长度存入列表并返回,整体时间复杂度 O (n),空间复杂度 O (1)(仅用固定长度的数组)。
总结
- 用最少箭引爆气球:贪心核心是「按右边界排序,一箭射穿最多重叠气球」,关键是选右边界最小的位置射箭。
- 无重叠区间:贪心核心是「按右边界排序,保留最多不重叠区间」,最少删除数 = 总数 - 最多保留数。
- 划分字母区间:贪心核心是「先记录字母最后位置,再找当前区间最大右边界」,本质是字符串版的重叠区间划分。