56. 合并区间
解题思路
这道题的贪心核心是:先按区间左边界排序,再逐个合并重叠 / 相邻区间(和之前的重叠区间题思路一脉相承)。
- 排序:将所有区间按左边界升序排列(确保按顺序处理,避免遗漏)。
- 合并区间 :
- 初始化结果列表,将第一个区间加入。
- 遍历后续区间:取出结果列表最后一个区间,判断当前区间是否与它重叠(当前区间左边界 ≤ 最后一个区间右边界)。
- 重叠:合并区间(更新最后一个区间的右边界为两者最大值)。
- 不重叠:将当前区间直接加入结果列表。
- 最终返回结果列表。
完整 Java 代码
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals.length <= 1) {
return intervals; // 0个或1个区间,无需合并
}
// 步骤1:按区间左边界升序排序
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
// 步骤2:初始化结果列表,加入第一个区间
List<int[]> merged = new ArrayList<>();
merged.add(intervals[0]);
// 步骤3:遍历并合并后续区间
for (int i = 1; i < intervals.length; i++) {
int[] last = merged.get(merged.size() - 1); // 结果列表最后一个区间
int[] curr = intervals[i]; // 当前遍历的区间
if (curr[0] <= last[1]) {
// 重叠,合并区间:更新最后一个区间的右边界
last[1] = Math.max(last[1], curr[1]);
} else {
// 不重叠,直接加入结果列表
merged.add(curr);
}
}
// 步骤4:转换为数组返回
return merged.toArray(new int[merged.size()][]);
}
}
代码解析
- 边界处理:区间数 ≤ 1 时,直接返回原数组(无需合并)。
- 排序关键:按左边界升序排序,保证处理顺序是 "从左到右",避免交叉处理导致逻辑混乱。
- 合并逻辑 :
- 用
List存储结果,方便动态修改最后一个区间。 - 每次取结果列表最后一个区间,对比当前区间的左边界:
- 重叠则更新右边界(取最大值,覆盖 "包含" 或 "部分重叠" 场景);
- 不重叠则直接添加当前区间。
- 用
- 结果转换 :将
List转为二维数组返回,符合题目要求。
738. 单调递增的数字
解题思路
这道题的贪心核心是:从后往前遍历,找到第一个破坏递增的位置,将该位置数字减 1,后续所有数字置为 9(保证最大的单调递增数)。
- 预处理:将数字转为字符数组(方便逐位修改)。
- 从后往前遍历 :
- 记录 "需要置为 9 的起始位置"
start = len(初始为数组长度,即无需置 9)。 - 从倒数第二位开始遍历:若当前位数字 > 后一位数字,说明破坏了递增,将当前位减 1,并更新
start为当前位 + 1(后续位都要置 9)。
- 记录 "需要置为 9 的起始位置"
- 置 9 操作 :将
start到数组末尾的所有位置为 9。 - 转换为数字返回。
完整 Java 代码
java
class Solution {
public int monotoneIncreasingDigits(int n) {
// 步骤1:转为字符数组,方便逐位处理
char[] digits = String.valueOf(n).toCharArray();
int len = digits.length;
int start = len; // 需要置为9的起始位置
// 步骤2:从后往前遍历,找破坏递增的位置
for (int i = len - 2; i >= 0; i--) {
if (digits[i] > digits[i + 1]) {
// 当前位 > 后一位,破坏递增
digits[i]--; // 当前位减1
start = i + 1; // 后续位都要置为9
}
}
// 步骤3:将start到末尾的位置为9
for (int i = start; i < len; i++) {
digits[i] = '9';
}
// 步骤4:转换为数字返回
return Integer.parseInt(new String(digits));
}
}
代码解析
- 字符数组转换:将数字转为字符数组,避免频繁的数学运算(取模、除法),更直观处理每一位。
- 从后往前遍历:核心是 "找到第一个破坏递增的位置",从后往前能避免重复处理(比如 332 → 先处理第二位 3>2,减为 2,start=2,最终置 9 得 329)。
- 置 9 逻辑 :
start标记了需要置 9 的起始位置,将这些位置置 9 能保证数字最大(比如 1000 → 遍历到第一位 1>0,减为 0,start=1,置 9 得 999)。 - 结果转换:将字符数组转回数字,注意处理前导 0(但题目要求单调递增,前导 0 会被自动处理为有效数字)。
贪心算法核心总结
结合之前所有题目,贪心算法的核心逻辑和解题技巧可总结为以下几点:
1. 贪心的本质
- 局部最优 → 全局最优:每一步都做出当前看起来最好的选择,无需回溯,最终得到全局最优解。
- 关键:证明局部最优能推导出全局最优(刷题时可通过 "直觉 + 案例验证",无需严格数学证明)。
2. 贪心的常见解题步骤
- 排序:绝大多数贪心题都需要先排序(如重叠区间按边界排序、股票按价格排序、身高队列按身高排序),排序是找到 "局部最优" 的前提。
- 找边界 / 基准:确定每一步的 "最优判断标准"(如箭的位置选右边界、合并区间看左边界、单调数字找破坏递增的位置)。
- 遍历处理:按排序后的顺序遍历,逐次应用局部最优策略,更新结果或边界。
3. 贪心的典型题型及策略
| 题型分类 | 核心策略 | 代表题目 |
|---|---|---|
| 重叠区间 | 按边界排序,找最优边界(左 / 右) | 452/435/763/56 |
| 数组 / 数字处理 | 优先处理极值(最小负数、最大差值) | 1005/738 |
| 路径 / 跳跃 | 维护最远可达位置 | 55/45 |
| 分配 / 找零 | 优先用大面额 / 保留关键资源 | 135/860 |
| 字符串 | 记录字符边界,按边界划分 | 763 |
4. 贪心的解题技巧
- 拆解问题:复杂问题拆分为 "单方向处理"(如分发糖果先左后右、重建队列先高后矮),避免两头兼顾导致逻辑混乱。
- 反向思考:如 "最少删除区间"→ 转化为 "最多保留区间",降低思考难度。
- 案例验证:用简单案例验证贪心策略是否正确(如 332 → 329,1000 → 999)。
5. 贪心的难点
- 贪心策略无固定模板,需要多刷题积累 "直觉";
- 部分题目需结合其他知识点(如二叉树、字符串),但核心仍是 "局部最优";
- 证明贪心的正确性较难,刷题时只需保证 "案例通过 + 逻辑自洽" 即可。
关键点回顾
- 合并区间:先按左边界排序,再逐个合并重叠区间,核心是 "更新右边界"。
- 单调递增的数字:从后往前找破坏递增的位置,减 1 后后续置 9,保证数字最大。
- 贪心核心:排序找局部最优边界,单方向处理,局部最优推导全局最优。