LeetCode 面试经典 150 题-区间
模拟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| class Solution { public List<String> summaryRanges(int[] nums) { List<String> res = new ArrayList<String>(); int n = nums.length; if (n == 0) { return res; } int pre = nums[0]; for (int i = 1; i < n; i++) { if (nums[i] != nums[i-1] + 1) { if (pre != nums[i-1]) { String str = pre + "->" + nums[i-1]; res.add(str); } else { res.add(String.valueOf(pre)); } pre = nums[i]; } }
if (pre != nums[n - 1]) { res.add(pre + "->" + nums[n - 1]); } else { res.add(String.valueOf(pre)); }
return res; } }
|
贪心
- 对区间按照起始点进行排序。
- 遍历区间,如果当前区间的起始位置小于上一个区间的结束位置,则说明可以合并区间,将区间的结束位置置为两区间结束位置的较大值。否则直接加入结果集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Solution { public int[][] merge(int[][] intervals) { Arrays.sort(intervals, (row1, row2) -> Integer.compare(row1[0], row2[0])); List<int[]> res = new ArrayList<>(); res.add(intervals[0]); for (int i = 1; i < intervals.length; i++) { int last[] = res.get(res.size()-1); if (intervals[i][0] <= last[1]) { last[1] = Math.max(last[1], intervals[i][1]); } else { res.add(intervals[i]); } } return res.toArray(new int[res.size()][]); } }
|
- 时间复杂度:O(nlogn)
- 空间复杂度:O(logn)
模拟
- 维护待插入区间的范围,维护是否在遍历过程中插入的标记;
- 遍历区间:
- 在插入区间右侧且无交集:如果还未插入,则插入,然后将当前区间加入结果;
- 在插入区间左侧且无交集:当前区间加入结果集;
- 有重叠:更新待插入区间的范围;
- 如果在遍历区间的时候没有插入新区间,说明整个区间都是重叠的,插入。
- 返回结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class Solution { public int[][] insert(int[][] intervals, int[] newInterval) { int left = newInterval[0]; int right = newInterval[1]; boolean placed = false; List<int[]> res = new ArrayList<>(); for (int[] interval : intervals) { if (interval[0] > right) { if (!placed) { res.add(new int[]{left, right}); placed = true; } res.add(interval);
} else if (interval[1] < left) { res.add(interval); } else { left = Math.min(left, interval[0]); right = Math.max(right, interval[1]); } } if (!placed) { res.add(new int[]{left, right}); }
return res.toArray(new int[res.size()][]); } }
|
贪心
将坐标按结束位置升序排序。对于第一个区间,假设弓箭射在其结束位置,往后依次遍历每个区间,如果弓箭的坐标小于区间的起始坐标,说明需要新的弓箭。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Solution { public int findMinArrowShots(int[][] points) { Arrays.sort(points, (p1, p2) -> Integer.compare(p1[1], p2[1])); int ans = 1; int arrow = points[0][1]; for (int i = 1; i < points.length; i++) { if (points[i][0] > arrow) { ans++; arrow = points[i][1]; } }
return ans; } }
|
相当于合并区间的时候取交集,最后返回交集区间数。
- 时间复杂度:O(nlogn)
- 空间复杂度:O(logn)
总结
排序
很多区间类问题都要求按区间的起始点或结束点排序,这是解题的第一步。排序后,可以更容易地判断区间之间的关系(是否有交集,是否可以合并等)。
- 排序依据:通常按区间的起始位置排序,或按结束位置排序。
- 排序后,简化问题:排序后的区间更容易处理,很多问题可以转换成遍历排序后的区间,逐一进行合并或插入。
区间的合并与插入
- 合并区间:遍历所有区间,遇到有交集的区间时,将它们合并成一个新区间。-
- 插入区间:对于给定的新区间,遍历现有区间,判断是否存在交集,若有交集则更新新区间的范围,否则直接插入。
贪心策略
贪心算法非常适合用于区间问题,特别是涉及到最小化操作的题目,如最少箭数射爆气球、区间调度问题等。通过选择当前最优的局部解来达到全局最优。典型的做法是按结束点或起始点对区间进行排序,之后选择最合适的区间进行处理。
区间的交集与并集
- 交集:两个区间是否有交集可以通过比较它们的起始点和结束点来判断。
- 并集:若两个区间有交集,则它们的并集区间就是从最小的起点到最大的终点。
边界条件处理
许多区间问题的边界条件处理需要特别注意,如空区间的情况、只有一个区间的情况等。