代码随想录算法训练营第三十六天 | 无重叠区间、划分字母区间、合并区间

435. 无重叠区间

leetcode
文档题解
视频题解

这道题和 452. 用最少数量的箭引爆气球 基本上一模一样,是一个思路。至少要删除几个区间才能使剩下的区间不重叠,其实就是在统计有几个区间重叠了。

public int eraseOverlapIntervals(int[][] intervals) {
        // 先按区间的起始位置排序
        Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));

        // 维护右边界
        int end = intervals[0][1];
        int result = 0;

        for (int i = 1; i < intervals.length; i++) {
            int[] curr = intervals[i];
            if (curr[0] < end) {
                // 重叠
                result++;
                // 为什么要取较小的作为边界
                // 这就是我们贪心的思路,取最小的就意味着删掉交叉、但右边界较大的区间
                // 这样可以减少较大区间对后续区间交叉的概率
                // 即删除交叉区间中较长(右边界较大)的那一个,减少对后续区间的影响
                end = Math.min(end, curr[1]);
            } else {
                end = curr[1];
            }
        }

        return result;
    }

763. 划分字母区间

leetcode
文档题解
视频题解

这个题需要理解一段话 “同一字母最多出现在一个片段中”,再加上题目要求不能更换字符顺序,因此可以理解为寻找遍历字符的最后出现的位置,如:“ababcbacadefegdehijhklij”,先遍历一遍,统计各个字符最后出现的位置,然后再遍历一遍,例如访问字符 a 时,它最终出现的下标是 8,那么第一个区间至少得到下标 8,再访问字符 b,如果它最终出现的下标是 10,那么第一个区间至少得到下标 10,加入后面的字符最终出现的位置没有超过 10 的,那么当遍历到 10 时就可以统计这段区间的长度了,然后继续按照上面的逻辑遍历。

class Solution {
    public List<Integer> partitionLabels(String s) {
        List<Integer> res = new LinkedList<>();

        // 记录每个字符最远出现的位置
        HashMap<Character, Integer> map = new HashMap<>();
        for (int i = 0; i < s.length(); i++) {
            map.put(s.charAt(i), i);
        }

        int start = 0;
        int end = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (map.get(c) > end) {
                end = map.get(c);
            }
            if (i == end) {
                res.add(i - start + 1);
                start = i + 1;
            }
        }

        return res;
    }
}

因为是 26 个小写字母,改用数组效率会更高。

class Solution {
    public List<Integer> partitionLabels(String s) {
        List<Integer> res = new LinkedList<>();

        // 记录每个字符最远出现的位置
        int[] map = new int[27];
        for (int i = 0; i < s.length(); i++) {
            map[s.charAt(i) - 'a'] = i;
        }

        int start = 0;
        int end = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (map[c - 'a'] > end) {
                end = map[c - 'a'];
            }
            if (i == end) {
                res.add(i - start + 1);
                start = i + 1;
            }
        }

        return res;
    }
}

据说和两个跳跃游戏是类似的思路?

56. 合并区间

leetcode
文档题解
视频题解

class Solution {
    public int[][] merge(int[][] intervals) {
        if (intervals.length == 0) return new int[0][];

        LinkedList<int[]> list = new LinkedList<>();
        Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));

        int start = intervals[0][0];
        int end = intervals[0][1];
        for (int i = 1; i < intervals.length; i++) {
            int[] curr = intervals[i];
            if (curr[0] <= end) {
                // 重叠,合并,只用更新右边界,即取较大的 end
                end = Math.max(end, curr[1]);
            } else {
                // 不重叠,收集结果,然后记录新的 start、end
                list.add(new int[]{start, end});
                start = curr[0];
                end = curr[1];
            }
        }

        // 加上最后一个区间
        list.add(new int[]{start, end});

        // int[][] res = new int[list.size()][];
        // int i = 0;
        // for (int[] e: list) {
        //   res[i++] = e;
        // }
        // return res;
        return list.toArray(new int[list.size()][]);
    }
}

这个题和 452. 用最少数量的箭引爆气球 以及上面的 “无重叠区间” 的思路是一样的。