YSMull
<-- algorithm



原题链接

思维方法

想象有一个数轴,有许多闭区间在这个数轴上,有些地方覆盖到了,有些地方没有覆盖到,我们要做的是,把能连起来的闭区间们合并一个大的闭区间。

解法一

我们对所有这些区间按照起始坐标排序,这样我们就可以从数轴的最左边向右遍历这些区间了,如果可以融合的就融合成一个区间,不能融合的就增加一个新的区间。

class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals, Comparator.comparingInt(arr -> arr[0]));
        LinkedList<int[]> res = new LinkedList<>();
        for (int[] cur : intervals) {
            if (res.isEmpty()) {
                res.add(cur);
            } else {
                int[] top = res.getLast();
                // 如果区间不相交
                if (top[0] > cur[1] || top[1] < cur[0]) {
                    res.add(cur);
                } else {
                    // 如果区间相交,则融合成一个新的区间
                    int[] newRange = {Math.min(top[0], cur[0]), Math.max(top[1], cur[1])};
                    res.removeLast();
                    res.add(newRange);
                }
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}

解法一的改进

解法一列出来的区间是否相交的条件,以及两个相交区间融合成一个新区间的写法,是比较通用的,但此题因为已经对区间按起始坐标进行了排序,对于区间 $[x_i, y_i]$ 和区间 $[x_{i+1}, y_{i+1}]$,一定有 $x_i \leq x_{i+1}$,所以:

  1. 区间不相交时一定是第二个区间在第一个区间的右边
  2. 两个区间融合成的新区间的起始坐标一定是第一个区间的起始坐标。
class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals, Comparator.comparingInt(arr -> arr[0]));
        LinkedList<int[]> res = new LinkedList<>();
        for (int[] cur : intervals) {
            if (res.isEmpty()) {
                res.add(cur);
            } else {
                int[] top = res.getLast();
                if (top[1] < cur[0]) { // ← 条件减弱
                    res.add(cur);
                } else { //            ↓ 坐标确定
                    int[] newRange = {top[0], Math.max(top[1], cur[1])};
                    res.set(res.size() - 1, newRange);
                }
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}

也可以简化一下代码,看起来短了很多:

class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals, Comparator.comparingInt(arr -> arr[0]));
        LinkedList<int[]> res = new LinkedList<>();
        for (int[] cur : intervals) {
            if (res.isEmpty() || res.getLast()[1] < cur[0]) {
                res.add(cur);
            } else {
                res.getLast()[1] = Math.max(res.getLast()[1], cur[1]);
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}

继续优化

如果我们对区间不只是按照起始坐标排序,在起始坐标相同的情况下,再根据结尾坐标排倒序,如图:

[[1,5], [1,4], [1,3], [2,6], [2,3], [3,1]]

我们扫描到 [1,5] 的时候,就可以跳过所有起始坐标为 1 的区间了

class Solution {
    public int[][] merge(int[][] intervals) {
        // Java 的 Comparator 是可链式组合的
        Arrays.sort(intervals, Comparator.comparingInt((int[] arr) -> arr[0])
                                         .thenComparingInt(arr -> -arr[1]));
        LinkedList<int[]> res = new LinkedList<>();
        for (int i = 0; i < intervals.length; i++) {
            int[] cur = intervals[i];
            if (res.isEmpty()) {
                res.add(cur);
            } else { // 此时 i 一定大于 0
                if (intervals[i - 1][0] == cur[0]) {
                    continue;
                }
                int[] top = res.getLast();
                if (top[1] < cur[0]) {
                    res.add(cur);
                } else {
                    int[] newRange = {top[0], Math.max(top[1], cur[1])};
                    res.set(res.size() - 1, newRange);
                }
            }
        }
        return res.toArray(new int[res.size()][]);
    }
}

简化一下代码,可以看得更清晰,为啥性能没多大提升。

class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals, Comparator.comparingInt((int[] arr) -> arr[0])
                                         .thenComparingInt(arr -> -arr[1])); // 1. 排序开销更大了
        LinkedList<int[]> res = new LinkedList<>();
        int[] prev = null;
        for (int[] cur : intervals) {
            if (res.isEmpty() || res.getLast()[1] < cur[0]) {
                res.add(cur);
            } else {
                if (prev[0] == cur[0]) continue; // 2. 减少了一个赋值操作,下面的 Max 也有比较操作
                res.getLast()[1] = Math.max(res.getLast()[1], cur[1]);
            }
            prev = cur; // 3. 这里增加了一个赋值操作
        }
        return res.toArray(new int[res.size()][]);
    }
}