LeetCode 435 - 无重叠区间


文章目录

    • 摘要
    • 描述
    • 题解答案(Swift)
    • 题解代码分析
    • [1. 为什么按"结束时间"排序?](#1. 为什么按“结束时间”排序?)
    • [2. 为什么用 `countNonOverlap = 1` 开始?](#2. 为什么用 countNonOverlap = 1 开始?)
    • [3. 如何判断重叠?](#3. 如何判断重叠?)
    • [4. 为什么结果是 `总区间数 - 不重叠的区间数`?](#4. 为什么结果是 总区间数 - 不重叠的区间数?)
    • [示例测试及结果(可运行 Demo)](#示例测试及结果(可运行 Demo))
    • 与实际场景结合
      • [1. 会议室预定冲突检测](#1. 会议室预定冲突检测)
      • [2. 排班系统](#2. 排班系统)
      • [3. 任务调度 / CPU 调度](#3. 任务调度 / CPU 调度)
      • [4. 数据清洗(归并日志 / 去重)](#4. 数据清洗(归并日志 / 去重))
    • 时间复杂度
    • 空间复杂度
    • 总结

摘要

这篇文章我们来聊一个在实际工作中经常遇到的经典问题 ------ "如何从一堆区间里删掉尽可能少的,让剩下的区间彼此之间没有重叠?"。

这道题在排班、会议室调度、任务分段处理、日程去重、数据清洗等真实业务场景中几乎都会遇到。算法本身不复杂,但要真正理解为什么这样做,需要一点点耐心。

我们会用 Swift 写出完整的解法代码,做可运行的 Demo,然后拆开讲每一步为什么这么做,让你在业务中也能轻松复用。

描述

题目给了我们一组区间:

txt 复制代码
intervals[i] = [start, end]

目标很简单:我们要移除尽可能少的区间,让剩下的所有区间互不重叠。

题目特别强调:

  • [1,2][2,3] 不算重叠。
  • 只有当"结束时间 > 下一个开始时间"才算真的 overlap。

示例:

txt 复制代码
[1,2] [2,3] [3,4] [1,3]

这里只有 [1,3] 和别人有重叠,只要移除这一段就够了,所以答案是 1

题解答案(Swift)

解决这类"区间调度"问题,最稳、最常见、最贪心的办法就是:

按照结束时间升序排序,然后每次优先选择最先结束的区间。

因为:

  • 一个区间结束得越早,就越不容易挡住后面的区间;
  • 保留更多不冲突的区间,自然就意味着删除更少。

完整代码如下:

swift 复制代码
class Solution {
    func eraseOverlapIntervals(_ intervals: [[Int]]) -> Int {
        if intervals.count <= 1 { return 0 }
        
        // 1. 按结束时间排序
        let sorted = intervals.sorted { $0[1] < $1[1] }
        
        var countNonOverlap = 1  // 至少能选一个
        var prevEnd = sorted[0][1]
        
        // 2. 贪心选择之后所有不重叠的区间
        for i in 1..<sorted.count {
            let interval = sorted[i]
            if interval[0] >= prevEnd {
                // 不重叠:选择它
                countNonOverlap += 1
                prevEnd = interval[1]
            }
        }
        
        // 3. 总区间数 - 不重叠区间数 = 需要移除的数量
        return intervals.count - countNonOverlap
    }
}

题解代码分析

下面我们把这段代码的核心逻辑拆开讲一下。

1. 为什么按"结束时间"排序?

比如有两段区间:

txt 复制代码
[1,3] 和 [1,2]

如果你优先选 [1,3],你就只能要一个。

但如果你选 [1,2],你后面还有机会选择更多不冲突的区间。

这就是贪心策略的核心:
尽量让每次选择留下更多空间给后面的区间。

2. 为什么用 countNonOverlap = 1 开始?

因为按照结束时间排好序后,最早结束的肯定是最佳选择,所以我们先选第一个。

3. 如何判断重叠?

非常简单:

swift 复制代码
if interval[0] >= prevEnd

如果下一个区间的开始时间 ≥ 上一个区间的结束时间,就不算重叠。

4. 为什么结果是 总区间数 - 不重叠的区间数

我们挑选尽量多的不冲突区间(maximize non-overlap),

而题目要求删除尽量少的(min remove)。

这两个是完全等价的反向操作。

示例测试及结果(可运行 Demo)

下面提供一段可运行的 Swift Playground Demo。

swift 复制代码
import Foundation

class Solution {
    func eraseOverlapIntervals(_ intervals: [[Int]]) -> Int {
        if intervals.count <= 1 { return 0 }
        
        let sorted = intervals.sorted { $0[1] < $1[1] }
        
        var countNonOverlap = 1
        var prevEnd = sorted[0][1]
        
        for i in 1..<sorted.count {
            let interval = sorted[i]
            if interval[0] >= prevEnd {
                countNonOverlap += 1
                prevEnd = interval[1]
            }
        }
        
        return intervals.count - countNonOverlap
    }
}

// Demo
let solution = Solution()

let test1 = [[1,2],[2,3],[3,4],[1,3]]
print("输入:", test1)
print("需要移除数量:", solution.eraseOverlapIntervals(test1), "\n")

let test2 = [[1,2],[1,2],[1,2]]
print("输入:", test2)
print("需要移除数量:", solution.eraseOverlapIntervals(test2), "\n")

let test3 = [[1,2],[2,3]]
print("输入:", test3)
print("需要移除数量:", solution.eraseOverlapIntervals(test3), "\n")

let test4 = [[1,5],[2,3],[3,4]]
print("输入:", test4)
print("需要移除数量:", solution.eraseOverlapIntervals(test4), "\n")

输出结果:

txt 复制代码
输入: [[1, 2], [2, 3], [3, 4], [1, 3]]
需要移除数量: 1

输入: [[1, 2], [1, 2], [1, 2]]
需要移除数量: 2

输入: [[1, 2], [2, 3]]
需要移除数量: 0

输入: [[1, 5], [2, 3], [3, 4]]
需要移除数量: 1

与实际场景结合

这个算法在真实业务里用得非常多,举几个常见场景:

1. 会议室预定冲突检测

如果你有一堆会议记录,需要清理成不冲突的会议安排,这个算法就是最优方案。

2. 排班系统

多个员工之间排班冲突,往往需要找出最少需要调整的区间。

3. 任务调度 / CPU 调度

操作系统里常见的调度算法,比如 Interval Scheduling Optimization,原理完全一样。

4. 数据清洗(归并日志 / 去重)

日志里经常有时间段交错的区间,清理成无重叠区间就是这类算法。

你会发现,几乎所有"时间段管理"类场景都能用这道题的思路解决。

时间复杂度

核心是排序:

  • 排序:O(n log n)
  • 遍历:O(n)

总复杂度:

O(n log n)

空间复杂度

我们只使用了常量空间(除了排序本身需要的空间):

O(1) 额外空间

(如果把排序的空间开销算进去则为 O(n),算法本体仍然是 O(1))

总结

LeetCode 435 是区间贪心问题中最典型的题之一。解法其实就一句话:

按结束时间排序 + 贪心选不重叠区间

这类题的核心不是写代码,而是理解为什么 "结束更早的区间更优"。一旦理解这个策略,所有类似的区间题基本都能秒掉。

相关推荐
sin_hielo1 小时前
leetcode 1018
算法·leetcode
大工mike1 小时前
代码随想录算法训练营第三十一天 | 1049. 最后一块石头的重量 II 494. 目标和 474.一和零
算法
import_random1 小时前
[机器学习]xgboost的2种使用方式
算法
橘颂TA2 小时前
【剑斩OFFER】算法的暴力美学——只出现一次的数字 ||
算法·leetcode·动态规划
想唱rap3 小时前
C++ map和set
linux·运维·服务器·开发语言·c++·算法
小欣加油4 小时前
leetcode 1018 可被5整除的二进制前缀
数据结构·c++·算法·leetcode·职场和发展
无敌最俊朗@4 小时前
链表-力扣hot100-随机链表的复制138
数据结构·leetcode·链表
WWZZ20255 小时前
快速上手大模型:深度学习12(目标检测、语义分割、序列模型)
深度学习·算法·目标检测·计算机视觉·机器人·大模型·具身智能