leetcode57. 插入区间

尝试写过,边界条件处理太笨比了,直接看答案

法一:三次遍历法
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
int i = 0, n = intervals.length;
List<int[]> resArray = new ArrayList<>();
// 第一阶段:处理所有结束点小于新区间开始点的区间(即完全在新区间左侧,无重叠)
// 这些区间与新区间无任何重叠,直接加入结果列表
while (i < n && intervals[i][1] < newInterval[0]) {
resArray.add(intervals[i]); ++i;
}
// 第二阶段:处理所有与新区间有重叠的区间,并进行合并
// 重叠条件:当前区间的开始点 <= 新区间的结束点
// 注意:在合并过程中,会动态更新新区间的边界,扩展其范围
while (i < n && newInterval[1] >= intervals[i][0]) {
// 合并区间的起始点取最小值(扩展左边界)
newInterval[0] = Math.min(intervals[i][0], newInterval[0]);
// 合并区间的结束点取最大值(扩展右边界)
newInterval[1] = Math.max(intervals[i][1], newInterval[1]);
++i; // 移动指针,继续检查下一个区间是否仍与合并后的新区间重叠
}
// 将合并后的最终新区间加入结果列表
// 注意:这个add操作只执行一次,因为所有重叠区间已被合并成一个
resArray.add(newInterval);
// 第三阶段:处理原区间列表中剩余的所有区间(即完全在合并后新区间右侧,无重叠)
// 这些区间无需任何处理,直接加入结果列表即可
while (i < n) {
resArray.add(intervals[i]);
++i;
}
// 将List<int[]>转换为int[][]并返回
// resArray.size()决定了返回二维数组的行数
return resArray.toArray(new int[resArray.size()][]);
}
}
法二:状态标志法
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
// 初始化新区间的左右边界
int left = newInterval[0];
int right = newInterval[1];
// 状态标志:标记新区间是否已被插入结果列表
boolean placed = false;
// 使用动态列表存储结果区间
List<int[]> result = new ArrayList<>();
// 遍历每个原有区间
for (int[] interval : intervals) {
if (interval[0] > right) {
// 情况1:当前区间在新区间右侧且无重叠,相当于上一种方法第三次遍历
if (!placed) {
// 若新区间未插入,先插入合并后的新区间
result.add(new int[]{left, right});
placed = true; // 更新标志位,表示新区间已插入
}
// 插入当前区间(在新区间右侧)
result.add(interval);
} else if (interval[1] < left) {
// 情况2:当前区间在新区间左侧且无重叠,直接插入,相当于法一第一次遍历
result.add(interval);
} else {
// 情况3:当前区间与新区间有重叠,合并区间,相当于法一第二次遍历
// 扩展新区间的左右边界以覆盖重叠部分
left = Math.min(left, interval[0]);
right = Math.max(right, interval[1]);
}
}
// 遍历结束后,若新区间仍未插入(说明位于所有区间右侧),则添加到末尾
if (!placed) {
result.add(new int[]{left, right});
}
// 将List转换为二维数组返回
return result.toArray(new int[result.size()][]);
}
}
法三:二分查找。某些情况下性能不如三次遍历法
class Solution {
public int[][] insert(int[][] intervals, int[] newInterval) {
// 处理原始区间列表为空的情况
if (intervals == null || intervals.length == 0) {
return new int[][]{newInterval};
}
int n = intervals.length;
List<int[]> result = new ArrayList<>();
// 1. 使用二分查找定位左边界:第一个满足 intervals[i][1] >= newInterval[0] 的区间索引
int leftIndex = findLeftBoundary(intervals, newInterval[0]);
// 2. 将 leftIndex 之前的所有区间加入结果(这些区间结束点都小于新区间开始点,绝对无重叠)
for (int i = 0; i < leftIndex; i++) {
result.add(intervals[i]);
}
// 3. 处理重叠区间:合并所有与新区间有重叠的区间
// 初始化合并区间的左右边界为新区间的值
int mergeStart = newInterval[0];
int mergeEnd = newInterval[1];
// 从 leftIndex 开始,遍历所有开始点小于等于新区间结束点的区间(即有重叠的区间)
int i = leftIndex;
while (i < n && intervals[i][0] <= newInterval[1]) {
// 扩展合并区间的边界
mergeStart = Math.min(mergeStart, intervals[i][0]);
mergeEnd = Math.max(mergeEnd, intervals[i][1]);
i++;
}
// 将合并后的区间加入结果
result.add(new int[]{mergeStart, mergeEnd});
// 4. 添加剩余区间(这些区间开始点都大于合并后区间的结束点,无重叠)
while (i < n) {
result.add(intervals[i]);
i++;
}
// 将List转换为二维数组返回
return result.toArray(new int[result.size()][]);
}
/**
* 二分查找法:寻找第一个满足 intervals[i][1] >= target 的索引 i。
* 即,第一个结束点大于等于目标值(新区间开始点)的区间位置。
* 如果所有区间的结束点都小于 target,则返回 n(表示新区间应插入在最末尾)。
*
* @param intervals 区间数组
* @param target 目标值,即新区间的开始点
* @return 第一个满足条件的区间索引,或 intervals.length
*/
private int findLeftBoundary(int[][] intervals, int target) {
int left = 0;
int right = intervals.length - 1;
int candidate = intervals.length; // 初始化为数组长度,表示默认在最后插入
// 标准的二分查找循环
while (left <= right) {
int mid = left + (right - left) / 2; // 防止整数溢出的写法
if (intervals[mid][1] >= target) {
// 当前中间点满足条件,它可能是一个候选,但我们还要看左边有没有更早满足条件的
candidate = mid; // 记录候选位置
right = mid - 1; // 继续在左半部分查找
} else {
// 当前中间点不满足条件,说明目标只可能出现在右半部分
left = mid + 1;
}
}
return candidate; // 返回找到的候选位置
}
}
浙公网安备 33010602011771号