在问 · 算法题解

435. 无重叠区间

中等动态规划按右端点贪心力扣原题 ↗

题目描述

输入区间数组 intervals,其中 intervals[i] = [start_i, end_i]。

从中删掉一些区间,让剩下的任意两个区间都没有重叠。返回最少需要删掉几个。

两个区间如果只在一个端点处相碰(例如 [1,2] 和 [2,3]),不算重叠。

示例 1
输入:intervals = [[1,2],[2,3],[3,4],[1,3]]
输出:1

解释:删去 [1,3] 后,其余区间两两只在端点相碰。

示例 2
输入:intervals = [[1,2],[1,2],[1,2]]
输出:2

解释:三个 [1,2] 相互重叠,只能留一个,删掉 2 个。

示例 3
输入:intervals = [[1,2],[2,3]]
输出:0

解释:两个区间只在 2 处相碰,不需要删。

数据范围

  • 1 <= intervals.length <= 10^5
  • intervals[i].length == 2
  • -5 * 10^4 <= start_i < end_i <= 5 * 10^4

解题思路:按右端点贪心

最少移除 = 总数 - 最多能保留的不重叠区间数。按右端点从小到大排序,依次扫描:如果左端点不小于上一个保留区间的右端 end,就保留它并更新 end。优先保留结束得早的区间,给后面留下的空间最多,交换论证可证明这样不会变差。

为什么正确

已保留的区间互不重叠,且在所有“同样保留 keep 个”的方案中,最后一个右端 end 最小。

复杂度分析

时间 O(n log n)(排序),额外空间 O(log n)。

易错点

按左端点排序后直接贪心会出错(一个很长的区间可能最先被保留)。另外判断用 s >= end,端点相接不算重叠。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key=lambda x: x[1])
        end = -math.inf
        keep = 0
        for s, e in intervals:
            if s >= end:
                keep += 1
                end = e
        return len(intervals) - keep

载入编辑器,自己跑一遍 →

其他解法

贪心与动态规划复杂度相同;贪心更短且省空间,动态规划更通用,区间带权时仍然适用。

解法 2:动态规划 + 二分查找 时间 O(n log n),空间 O(n)

按右端点排序。dp[i] 表示只看前 i 个区间时最多能保留几个互不重叠的区间。对第 i 个区间,二分找出右端点不超过它左端点的区间个数 j。转移为 dp[i+1] = max(dp[i], dp[j] + 1)。答案是 n - dp[n]。

不需要证明贪心正确性,思路可以推广到带权区间调度。需要 O(n) 的 dp 数组,代码比贪心长。

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        intervals.sort(key=lambda x: x[1])
        n = len(intervals)
        ends = [e for _, e in intervals]
        # dp[i]:只看前 i 个区间(按右端点排序)时最多保留几个
        dp = [0] * (n + 1)
        for i, (s, e) in enumerate(intervals):
            j = bisect_right(ends, s, 0, i)
            dp[i + 1] = max(dp[i], dp[j] + 1)
        return n - dp[n]

动态规划 专题的其他题目

查看 动态规划 全部题目 →