合并区间
给出一个区间的集合,请合并所有重叠的区间。
示例 1:
输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]
示例 2:
输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间
先将元素按照左端点排序
用数组 merged 存储最终的答案。
将第一个区间加入 merged 数组中,并按顺序依次考虑之后的每个区间:
如果当前区间的左端点在数组 merged 中最后一个区间的右端点之后,那么它们不会重合,我们可以直接将这个区间加入数组 merged 的末尾;
否则,它们重合,我们需要用当前区间的右端点更新数组 merged 中最后一个区间的右端点,将其置为二者的较大值。
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, Comparator.comparingInt(o -> o[0]));
int n = 0;
List<int[]> list = new ArrayList<>();
for (int[] ints : intervals) {
if (!list.isEmpty()) {
int[] interval = list.get(list.size() - 1);
if (ints[0] <= interval[1]) {
interval[1] = Math.max(interval[1], ints[1]);
continue;
}
}
list.add(ints);
}
int[][] ans = new int[n][2];
return list.toArray(ans);
}
时间复杂度O(nlogn)
无重叠区间
给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。
注意:
- 可以认为区间的终点总是大于它的起点。
- 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。
示例 1:
输入: [ [1,2], [2,3], [3,4], [1,3] ]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。
示例 2:
输入: [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。
示例 3:
输入: [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了
方法一:动态规划
先按开始时间排序,dp[i]表示前考虑前i个区间时的区间个数(重合的要合并)
最终结果为初始区间个数-最大区间个数
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals == null || intervals.length <= 1) {
return 0;
}
Arrays.sort(intervals, Comparator.comparingInt(o -> o[0]));
int[] dp = new int[intervals.length];
Arrays.fill(dp, 1);
for (int i = 1; i < intervals.length; i++) {
for (int j = i - 1; j >= 0; j--) {
if (intervals[i][0] >= intervals[j][1]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
return intervals.length - dp[intervals.length - 1];
}
时间复杂度O(n2)
方法二:贪心(左边界排序)
按开始时间排序,考虑当前区间与前一个区间,共三种情况:
- 不相交,下一次考虑的前一个区间即为当前区间
- 前一个区间包含了当前区间,删除前一个区间,下一次考虑的前一个区间即为当前区间
- 前一个区间与当前区间部分重叠,删除当前区间,一次考虑的前一个区间不变
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals == null || intervals.length <= 1) {
return 0;
}
Arrays.sort(intervals, Comparator.comparingInt(o -> o[0]));
int count = 0;
int[] pre = intervals[0];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] < pre[1]) {
if (pre[1] > intervals[i][1]) {
pre = intervals[i];
}
count++;
continue;
}
pre = intervals[i];
}
return count;
}
时间复杂度O(nlogn)
方法三:右边界排序
public int eraseOverlapIntervals(int[][] intervals) {
if (intervals.length < 1) {
return 0;
}
Arrays.sort(intervals, (Comparator.comparingInt(o -> o[1])));
int n = intervals.length;
int res = 0;
int right = intervals[0][1];
for (int i = 1; i < n; i++) {
if (intervals[i][0] < right) {
res++;
} else {
right = intervals[i][1];
}
}
return res;
}
删除被覆盖区间
给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。
只有当 c <= a
且 b <= d
时,我们才认为区间 [a,b)
被区间 [c,d)
覆盖。
在完成所有删除操作后,请你返回列表中剩余区间的数目。
示例:
输入:intervals = [[1,4],[3,6],[2,8]]
输出:2
解释:区间 [3,6] 被区间 [2,8] 覆盖,所以它被删除了
待完善的思路:
按照开始时间排序,如果前一个区间覆盖了当前区间,覆盖了的区间个数加1,下次考虑的前一个区间不变,否则下次考虑的前一个区间为当前区间
存在一个问题:存在开始相同的区间,如[[1,2],[1,4],[3,4]]
,这种情况要再根据结束时间排序,后结束的排在前面
public int removeCoveredIntervals(int[][] intervals) {
if (intervals.length <= 1) {
return 1;
}
Arrays.sort(intervals, (o1, o2) -> o1[0] - o2[0] == 0 ? o2[1] - o1[1]: o1[0] - o2[0]);
int count = 0;
int[] pre = intervals[0];
for (int i = 1; i < intervals.length; i++) {
if (intervals[i][0] <= pre[1]) {
if (pre[1] >= intervals[i][1]) {
count++;
continue;
}
}
pre = intervals[i];
}
return intervals.length - count;
}
插入区间
给出一个无重叠的 ,按照区间起始端点排序的区间列表。
在列表中插入一个新的区间,你需要确保列表中的区间仍然有序且不重叠(如果有必要的话,可以合并区间)。
示例 1:
输入:intervals = [[1,3],[6,9]], newInterval = [2,5]
输出:[[1,5],[6,9]]
示例 2:
输入:intervals =
[[1,2],[3,5],[6,7],[8,10],[12,16]]
, newInterval =[4,8]
输出:[[1,2],[3,10],[12,16]]
解释:这是因为新的区间[4,8]
与[3,5],[6,7],[8,10]
重叠
方法一:添加+合并区间
先将新区间加到数组中,利用合并区间的方式对新数组进行处理
public int[][] insert(int[][] intervals, int[] newInterval) {
int n = intervals.length;
int[][] temp = new int[n + 1][2];
System.arraycopy(intervals, 0, temp, 0, intervals.length);
temp[n] = newInterval;
return merge(temp);
}
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, Comparator.comparingInt(o -> o[0]));
int n = 0;
List<int[]> list = new ArrayList<>();
for (int[] ints : intervals) {
if (!list.isEmpty()) {
int[] interval = list.get(list.size() - 1);
if (ints[0] <= interval[1]) {
interval[1] = Math.max(interval[1], ints[1]);
continue;
}
}
list.add(ints);
}
int[][] ans = new int[n][2];
return list.toArray(ans);
}
方法二:
分成三段处理:
- 结束时间比新区间小,说明不重叠
- 合并中间重叠的区间
- 开始时间比新区间大,说明不重叠
public int[][] insert(int[][] intervals, int[] newInterval) {
int n = intervals.length;
int[][] res = new int[n + 1][2];
int i = 0, j = 0;
// 将结束时间比新区间开始时间小的加到结果集中,因为肯定不重叠
while (i < n && intervals[i][1] < newInterval[0]) {
res[j++] = intervals[i++];
}
// 处理重叠的区间
res[j] = newInterval;
// 得到最小的开始时间
if (i < n && intervals[i][0] < newInterval[0]) {
res[j][0] = intervals[i][0];
}
// 得到最大的结束时间
while (i < n && intervals[i][0] <= newInterval[1]) {
res[j][1] = Math.max(res[j][1], intervals[i++][1]);
}
j++;
// 之后的区间是开始时间比新区间结束时间大,说明不重叠
while (i < n) {
res[j++] = intervals[i++];
}
// 最终区间可能没有n + 1个
return Arrays.copyOf(res, j);
}
汇总区间
给定一个无重复元素的有序整数数组,返回数组区间范围的汇总。
示例 1:
输入: [0,1,2,4,5,7]
输出: ["0->2","4->5","7"]
解释: 0,1,2 可组成一个连续的区间; 4,5 可组成一个连续的区间
示例 2:
输入: [0,2,3,4,6,8,9]
输出: ["0","2->4","6","8->9"]
解释: 2,3,4 可组成一个连续的区间; 8,9 可组成一个连续的区间
public List<String> summaryRanges(int[] nums) {
List<String> res = new ArrayList<>();
int i = 0;
while (i < nums.length) {
int j = i + 1;
while (j < nums.length && nums[j] == nums[j - 1] + 1) {
j++;
}
if (j != i + 1) {
res.add(nums[i] + "->" + nums[j - 1]);
} else {
res.add(String.valueOf(nums[i]));
}
i = j;
}
return res;
}
763. 划分字母区间
字符串 S
由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。
示例:
输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。
贪心
由于同一个字母只能出现在同一个片段,显然同一个字母的第一次出现的下标位置和最后一次出现的下标位置必须出现在同一个片段。因此需要遍历字符串,得到每个字母最后一次出现的下标位置。
public List<Integer> partitionLabels(String s) {
int[] last = new int[26];
for (int i = 0; i < s.length(); i++) {
last[s.charAt(i) - 'a'] = i;
}
int start = 0, end = 0;
List<Integer> res = new ArrayList<>();
for (int i = 0; i < s.length(); i++) {
end = Math.max(end, last[s.charAt(i) - 'a']);
if (i == end) {
res.add(end - start + 1);
start = i + 1;
}
}
return res;
}
用最少数量的箭引爆气球
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
给你一个数组 points
,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。
示例 1:
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:对于该样例,x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球
题目意思是:给定n个闭区间[x,y],问最少需要确定多少个点,使得每个闭区间中都至少存在一个点。
排序+贪心
按起点排序,如果有重叠的区间
public int findMinArrowShots(int[][] points) {
// 没有用减法防止溢出
Arrays.sort(points, (o1, o2) -> {
if (o1[0] > o2[0]) {
return 1;
}
if (o1[0] < o2[0]) {
return -1;
}
return 0;
});
int res = 0;
int minEnd = Integer.MAX_VALUE;
// 对每一个开始的区间记录一个点,然后找能和该区间重叠的区间
for (int i = 0; i < points.length; i++) {
if (i > 0 && points[i][0] <= minEnd) {
minEnd = Math.min(minEnd, points[i][1]);
} else {
res++;
minEnd = points[i][1];
}
}
return res;
}