【435. 无重叠区间】中等题(有点难理解)
思路:
1、对所有区间按【左边界】升序排序。
2、统计需要移除区间的最小数量,使剩余区间互不重叠。利用right记录之前区间重叠区域的右边界,每遍历一个区间,考虑如何更新right:
- 如果当前遍历区间与之前的重叠区域【重叠】,即 intervals[i][0] < right,则重叠区间个数+1,更新right为当前区间右边界和right的最小值;
- 如果当前遍历区间与之前的重叠区域【不重叠】,即 intervals[i][0] >= right,则更新right为当前区间的右边界。
理解:
- 3个区间都有重叠的区域,第三个区间的左边界小于前两个区间的重叠区域的右边界,则需要删除2个区间(左图)。
- 3个区间只有其中两两各自重叠,第三个区间的左边界大于/等于前两个区间的重叠区域的右边界,则只需要删除1个区间(右图)。
相似题目:【452. 用最少数量的箭引爆气球】
- 区别在于,452题统计的是使剩余区间相互独立的【保留下来的不重叠的区间个数】,435题统计的是使剩余区间相互独立的【需要删除的重叠区间的个数】,两者相加等于所有区间的个数。
- 相同在于,都需要在遍历区间时更新前面区间重叠区域的右边界,再判断当前区间是否与重叠区域重叠。
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
Arrays.sort(intervals, (o1, o2) -> Integer.compare(o1[0], o2[0]));
int cover = 0;
int right = intervals[0][1];
for (int i = 1; i < intervals.length; i++){
if (intervals[i][0] < right){
cover++;
right = Math.min(intervals[i][1], right);
}
else{
right = intervals[i][1];
}
}
return cover;
}
}
- 时间复杂度: O(nlogn)
- 空间复杂度: O(logn),快速排序递归调用栈的深度
【763.划分字母区间】中等题
方法一 转化为重叠区间问题求解
思路:
1、使用LinkedHashMap使存储顺序与输入顺序相同,后序就不需要排序,key为出现过的字母,value为LinkedList(存储key对应字母出现的索引)
2、用二维数组存储所有区间,每个区间的第一个元素为字母第一次出现的索引,第二个元素为字母最后一次出现的索引
3、根据当前区间与pre是否重叠,进而判断是否划分片段
- 当前区间与pre有重叠,不能划分片段,扩展pre的右边界为两个右边界的最大值
- 当前区间与pre没有重叠,将上个区间pre划分为片段,计算pre的长度并加入res,并更新pre为当前区间。
(注意:每次划分片段都是在片段的下一个区间进行的,因此最后一个片段得单独处理)
示例:
相似题目:
【452.用最少数量的箭引爆气球 】- 求保留下来的【不重叠区间的个数】
【435.无重叠区间】- 求需要删除的【重叠区间的个数】
【763. 划分字母区间】(本题) - 求【合并重叠区间后】的【不重叠的区间个数】
class Solution {
public List<Integer> partitionLabels(String s) {
// 使用LinkedHashMap使存储顺序与输入顺序相同,后序就不需要排序
// key: 出现过的字母,value:LinkedList(存储key对应字母出现的索引)
Map<Character, List<Integer>> map = new LinkedHashMap<>();
for (int i = 0; i < s.length(); i++){
char c = s.charAt(i);
if (map.containsKey(c)){
map.get(c).add(i);
}
else{
List<Integer> list = new LinkedList<>();
list.add(i);
map.put(c, list);
}
}
System.out.println(map);
System.out.println("-------------------------------");
// 用二维数组存储所有区间,每个区间的第一个元素为字母第一次出现的索引,第二个元素为字母最后一次出现的索引
int[][] intervals = new int[map.size()][2];
int idx = 0;
for (char c : map.keySet()){
intervals[idx][0] = map.get(c).getFirst();
intervals[idx][1] = map.get(c).getLast();
System.out.println(Arrays.toString(intervals[idx]));
idx++;
}
// 根据当前区间与pre是否重叠,进而判断是否分割片段
List<Integer> res = new ArrayList<>();
int[] pre = intervals[0];
for (int i = 1; i < intervals.length; i++){
int[] interval = intervals[i];
// 当前区间与pre有重叠时
if (interval[0] < pre[1]){
pre[1] = Math.max(interval[1], pre[1]); // 不能划分片段,扩展pre的右边界为两个右边界的最大值
}
// 当前区间与pre没有重叠
else {
res.add(pre[1] - pre[0] + 1); //将上个区间pre划分为片段,计算pre的长度并加入res
pre = interval; // 更新pre为当前区间
}
}
res.add(pre[1] - pre[0] + 1); // 每次划分片段都是在片段的下一个区间进行的,最后一个片段得单独处理
return res;
}
}
- 时间复杂度: O(n),遍历字符串
- 空间复杂度: O(1),最多26个字母
方法二 动态更新片段右边界
思路:
1、遍历字符串,用哈希表记录每种字母最后一次出现的索引
2、遍历字符串,更新片段的右边界,直至右边界和当前索引一致,才划分片段,以确保同种字母只能在同一个片段中出现。
示例:
class Solution {
public List<Integer> partitionLabels(String s) {
// 遍历字符串,用哈希表记录每种字母最后一次出现的索引
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++){
char c = s.charAt(i);
map.put(c, i);
}
System.out.println(map);
System.out.println("------------");
// 遍历字符串,更新片段的右边界,直至右边界和当前索引一致,才划分片段
List<Integer> res = new ArrayList<>();
int end = 0;
int preEnd = -1;
for (int i = 0; i < s.length(); i++){
end = Math.max(end, map.get(s.charAt(i)));
System.out.print(i + ":" + s.charAt(i) + "[" + end + "] ");
if (i == end) {
res.add(end - preEnd);
preEnd = end;
System.out.println();
}
}
return res;
}
}
- 时间复杂度: O(n)
- 空间复杂度: O(1),最多26个字母
【56. 合并区间】中等题(做完前面的题后,很简单)
思路:
获取返回列表的最后一个区间,判断是否与当前区间重叠,进而判断是否需要合并区间
- 如果最后一个区间与当前区间有重叠,则删除最后一个区间,添加合并后的区间到返回列表中,注意合并区间的右边界应该是两区间右边界的最大值;
- 如果最后一个区间与当前区间没有重叠,则不需要合并区间,直接添加当前区间到返回列表中即可。
class Solution {
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (o1, o2) -> Integer.compare(o1[0], o2[0]));
List<int[]> list = new LinkedList<>();
list.add(intervals[0]);
for (int i = 1; i < intervals.length; i++){
int[] last = list.getLast();
if (intervals[i][0] <= last[1]){
list.removeLast();
list.addLast(new int[]{last[0], Math.max(last[1], intervals[i][1])});
}
else{
list.addLast(intervals[i]);
}
}
return list.toArray(new int[list.size()][]);
}
}
- 时间复杂度: O(nlogn),快速排序
- 空间复杂度: O(n),额外的链表开销