【力扣一刷】代码随想录day36(贪心算法part5 - 重叠区间专题:435. 无重叠区间、763.划分字母区间、56. 合并区间 )

【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),额外的链表开销
  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值