区间相关&用最少数量的箭引爆气球

合并区间

给出一个区间的集合,请合并所有重叠的区间。

示例 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. 区间 [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 <= ab <= 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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341