【LeetCode】2406、将区间分为最少组数

【LeetCode】2406、将区间分为最少组数

文章目录

一、数据结构-堆、贪心

1.1 数据结构-堆、贪心

题目已知一些区间, 需要尽量合并, 使 组 最少.

可以用图解画一下

  1. 因为尽量合并, 为了紧凑, 尽量按 left 从小到大 依次处理. 而题目答案也和顺序无关(因为只需要组的数目), 所以可以排序.

  2. 以示例一为例, 如果已经有 [1, 5] 的情况下, [1, 10] 因为和 [1, 5] 有交集所以必须单独成组. 则分为两个组, 如下

  3. 接下来, 再放 [2, 3], 其实 [2, 3] 也和 [1, 5] 重叠 (核心是 [2, 3] 的左端点2, 小于 [1, 5] 的右端点 5), 所以无法和 [1, 5] 合并, 而必须单独成组, 如下:

  4. 接下来, 再放 [5, 10], 其实只需要考虑已有各组的最小的 右端点(即 [2, 3] 的 3 是最小的), 而 [5, 10] 和 [2, 3] 并不相交, 所以可以合并

  5. 接下来, 再放 [6, 8], 此时最小的右端点的组为 [1, 5], 而 [6, 8] 和 [1, 5] 并不相交, 所以可以合并为一个组, 如下:

通过推导, 可总结出

  1. 如果新区间 可 和 右端点最小的组 合并, 则是最紧凑的, 则应合并尽量合并, 并更新 "右端点最小的组的 新右端点" 这是贪心的思想.
  2. 否则, 新区间 应 单独成组.

而如何快速找到 "右端点最小的组", 且更新 "右端点最小的组的 新右端点" 呢? => 堆 => 最小堆维护各组的右端点即可

所以伪代码如下:

  1. 按左端点, 排序各区间
  2. 准备一个堆(其中放的是各组的 right), 遍历各区间
    2.1 若 "新区间的 left" > "right最小的组的 right", 则意味着可合并二者, 则把堆顶替换为 "新区间的 right" 并 heapify 调整堆
    2.2 若 "新区间的 left" <= "right最小的组的 right" 或 堆为空, 则需单独成组
  3. 最终 堆的大小 即为组的个数, 即为答案
go 复制代码
// go
func minGroups(intervals [][]int) int {
    sort.Slice(intervals, func(i, j int) bool {
        return intervals[i][0] < intervals[j][0] // 按左端点排序
    })
    h := hp{}
    for _, p := range intervals {
        left, right := p[0], p[1]
        if h.Len() > 0 && left > h.IntSlice[0] {
            h.IntSlice[0] = right // 把新区间的right, 放入堆顶
            heap.Fix(&h, 0) // heapify 堆顶 (第0下标的就是堆顶)
        } else {
            heap.Push(&h, right)
        }
    }
    return h.Len()
}

type hp struct {sort.IntSlice}
func (h *hp) Push(v any) {h.IntSlice = append(h.IntSlice, v.(int))}
func (h *hp) Pop() any {a := h.IntSlice; v := a[len(a)-1]; h.IntSlice = a[:len(a)-1]; return v}

灵神视频
灵神题解

1.2 多语言解法

C p p / G o / P y t h o n / R u s t / J s / T s Cpp/Go/Python/Rust/Js/Ts Cpp/Go/Python/Rust/Js/Ts

cpp 复制代码
// cpp
go 复制代码
// go 同上
python 复制代码
# python
class Solution:
    def minGroups(self, intervals: List[List[int]]) -> int:
        intervals.sort()
        h = []
        for left, right in intervals: # [[l1, r1], [l2, r2], [l3, r3]]
            if h and left > h[0]: # 可合并 新区间[l,r] 和 老组, 其中 h[0] 是堆顶(表示 最小right的组 的right)
                heapreplace(h, right) # 把新区间的right, 作为该组(合并后的组) 的新right
            else: # 无法合并, (有交集, 或堆为空), 则新区间需单独成组
                heappush(h, right)
        return len(h)
rust 复制代码
// rust
js 复制代码
// js
ts 复制代码
// ts

二、扫描线

2.1 扫描线

题目本质是问, 同一时刻, 最多有几个组 (和253题: 同一时刻最多几个会议室 是同样的模板)

可以用差分数组, 记录 会议的变化(增加或减少会议)

如上图所示:

1时刻增加二个会议室 (因[1, 5] 增加一个, 因 [1, 10] 增加一个)

2时刻增加一个会议室 (因[2, 3] 增加一个)

3时刻减少一个会议室 (因[2, 3] 减少一个)

5时刻增加一个会议室 (因 [5, 10] 增加一个)

5时刻减少一个会议室 (因 [1, 5] 减少一个)

6时刻增加一个会议室 (因 [6, 8] 增加一个)

8时刻减少一个会议室 (因 [6, 8] 减少一个)

注意边界条件为5时刻, 虽然同时增加且减少一个, 但还是不能相互抵消, 即如下图需要先从2增加到3再减少回2. 毕竟此时刻两个会议室都需要被占用(因为前一个会议室还没有结束嘛)

![]](https://i-blog.csdnimg.cn/direct/b57cde44df3d4566bd769978433e3017.png)

所以为了处理这种边界条件, 可视为 [left, right] 的闭区间为 [left, right+1] 的闭区间, 即视为 right +1 会议才结束

go 复制代码
func minGroups(intervals [][]int) (ans int) {
    diff := map[int]int{}
    for _, p := range intervals {
        left, right := p[0], p[1]
        diff[left]++
        diff[right+1]--
    }

    // ds 排序, 从而方便从小到大遍历
    keys := []int{}
    for k := range diff { keys = append(keys, k) } // 收集的是下标
    slices.Sort(keys)

    sum := 0 // 差分数组, 求前缀和, 得到的当前时刻的会议室数目
    for _, k := range keys {
        sum += diff[k] // 通过k找到对应的会议室数目 diff[k]
        ans = max(ans, sum) // d 为会议室的变化, sum 为 会议室的数目, ans 为最多的会议室数目
    }
    return ans
}

残酷视频

相关推荐
查理零世3 小时前
【算法】数论基础——约数个数定理、约数和定理 python
python·算法·数论
汉克老师4 小时前
GESP2024年3月认证C++六级( 第三部分编程题(1)游戏)
c++·学习·算法·游戏·动态规划·gesp6级
闻缺陷则喜何志丹4 小时前
【C++图论】2685. 统计完全连通分量的数量|1769
c++·算法·力扣·图论·数量·完全·连通分量
利刃大大4 小时前
【二叉树深搜】二叉搜索树中第K小的元素 && 二叉树的所有路径
c++·算法·二叉树·深度优先·dfs
CaptainDrake4 小时前
力扣 Hot 100 题解 (js版)更新ing
javascript·算法·leetcode
一缕叶5 小时前
洛谷P9420 [蓝桥杯 2023 国 B] 子 2023 / 双子数
算法·蓝桥杯
甜甜向上呀5 小时前
【数据结构】空间复杂度
数据结构·算法
Great Bruce Young5 小时前
GPS信号生成:C/A码序列生成【MATLAB实现】
算法·matlab·自动驾驶·信息与通信·信号处理
Mryan20055 小时前
LeetCode | 不同路径
数据结构·c++·算法·leetcode
windwind20005 小时前
纪录片《寿司之神》杂感
程序人生·游戏·职场和发展·创业创新·个人开发·游戏策划