编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • 数据结构与算法专栏
  • 基础认知

  • 线性结构

  • 树与哈希

  • 工业级实现

  • 算法思想

  • 实战与综合

  • 算法题考核

    • README
    • 数组算法题合集
    • 链表算法题合集
    • 栈队列算法题合集
    • 哈希算法题合集
    • 二叉树算法题合集
    • 堆算法题合集
    • 图算法题合集
    • 二分查找算法题合集
    • 双指针算法题合集
    • 排序算法题合集
    • 分治算法题合集
      • 最小区间(Smallest Range Covering Elements from K Lists)
        • 01. 题目描述
        • 02. 题目分析
        • 03. 代码
        • 04. 与合并K个有序链表的对比
        • 05. 总结
    • 贪心算法题合集
    • 回溯算法题合集
    • 动规算法题合集
    • 位运算算法题合集
    • 数学算法题合集
  • 算法
  • 算法题考核
杨充
2017-09-24
目录

分治算法题合集

# 最小区间(Smallest Range Covering Elements from K Lists)

LeetCode 632 · ⭐⭐⭐ · 多路归并堆

K 个有序数组,找一个最小区间使得区间内至少包含每个数组中的一个元素。这道题是多路归并的经典应用——与合并K个有序链表同根同源。

# 01. 题目描述

给定 K 个升序排列的整数列表。找出一个最小区间 [left, right],使得每个列表中至少有一个元素在区间内。若有多个,返回起点最小的。

示例:

输入:nums = [[4,10,15,24,26],[0,9,12,20],[5,18,22,30]]
输出:[20,24]
解释:
  [20,24] 包含: 列表1的20和24, 列表2的20, 列表3的22
  区间长度=4, 是所有可行区间中最小的
1
2
3
4
5

约束:nums[i] 升序,元素总数 ≤ 10^5。

# 02. 题目分析

2.1 朴素思路

枚举所有区间 → O(元素总数²) → 不可接受。

2.2 多路归并视角

flowchart TD
    A["每个列表取当前最小元素"] --> B["维护当前窗口[min, max]"]
    B --> C["max-min = 候选区间长度"]
    C --> D["弹堆: 弹出最小值所在列表的下一个元素"]
    D --> E["堆中放入新元素 → 更新max"]
    E --> F{"某个列表耗尽?"}
    F -->|NO| B
    F -->|YES| G["结束: 返回最优区间"]
1
2
3
4
5
6
7
8

2.3 核心洞察

用最小堆维护 每个列表当前参与窗口的最小元素。max 跟踪窗口右边界。

  • 弹堆顶(min):当前窗口的最左端
  • 替换为同列表的下一个元素:保证窗口始终包含所有列表
  • 更新 window = [min, max]:如果更小则记录

2.4 为什么这样能找到最优?

当前窗口的最小值是某个列表的元素。要缩小窗口,必须放弃它——换上同列表的下一个更大元素。所以每次都弹出最小值所在列表的下一个。

2.5 手推演示

nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]

初始化:每个列表首元素入堆
pq=[(0,1,0), (4,0,0), (5,2,0)]    max=5
格式: (值, 列表号, 该列表中的下标)

窗口[0,5], len=6

弹(0,1,0): 列表1下一个=9 → 入(9,1,1), max=9
  pq=[(4,0,0),(5,2,0),(9,1,1)]  窗口[4,9], len=6(不优)

弹(4,0,0): 列表0下一个=10 → 入(10,0,1), max=10
  pq=[(5,2,0),(9,1,1),(10,0,1)] 窗口[5,10], len=6

弹(5,2,0): 列表2下一个=18 → 入(18,2,1), max=18
  pq=[(9,1,1),(10,0,1),(18,2,1)] 窗口[9,18], len=10

弹(9,1,1): 列表1下一个=12 → 入(12,1,2), max=18
  pq=[(10,0,1),(12,1,2),(18,2,1)] 窗口[10,18], len=9

弹(10,0,1): 列表0下一个=15 → 入(15,0,2), max=18
  pq=[(12,1,2),(15,0,2),(18,2,1)] 窗口[12,18], len=7

弹(12,1,2): 列表1下一个=20 → 入(20,1,3), max=20
  pq=[(15,0,2),(18,2,1),(20,1,3)] 窗口[15,20], len=6

弹(15,0,2): 列表0下一个=24 → 入(24,0,3), max=24
  pq=[(18,2,1),(20,1,3),(24,0,3)] 窗口[18,24], len=7

弹(18,2,1): 列表2下一个=22 → 入(22,2,2), max=24
  pq=[(20,1,3),(22,2,2),(24,0,3)] 窗口[20,24], len=4 ← 最优!

弹(20,1,3): 列表1下一个=不存在(长度3,下标3越界)
  堆大小<3 → 结束

最终: [20,24]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 03. 代码

Java:

public int[] smallestRange(List<List<Integer>> nums) {
    // 堆: [值, 列表索引, 元素在列表中的下标]
    PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
    int max = Integer.MIN_VALUE, start = 0, end = Integer.MAX_VALUE;

    // 初始:每个列表第一个元素入堆
    for (int i = 0; i < nums.size(); i++) {
        int val = nums.get(i).get(0);
        pq.offer(new int[]{val, i, 0});
        max = Math.max(max, val);
    }

    while (pq.size() == nums.size()) {           // 必须所有列表都有元素
        int[] cur = pq.poll();                    // 当前最小值
        int min = cur[0], listIdx = cur[1], elemIdx = cur[2];

        // 更新最优区间
        if (max - min < end - start) { start = min; end = max; }

        // 用同列表的下一个元素替换
        if (elemIdx + 1 < nums.get(listIdx).size()) {
            int nxt = nums.get(listIdx).get(elemIdx + 1);
            pq.offer(new int[]{nxt, listIdx, elemIdx + 1});
            max = Math.max(max, nxt);             // 更新最大值
        }
    }
    return new int[]{start, end};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Python:

def smallestRange(self, nums: List[List[int]]) -> List[int]:
    import heapq
    pq = [(lst[0], i, 0) for i, lst in enumerate(nums)]
    heapq.heapify(pq)

    mx = max(lst[0] for lst in nums)
    start, end = pq[0][0], mx

    while len(pq) == len(nums):
        _, list_i, elem_i = heapq.heappop(pq)
        if mx - pq[0][0] < end - start:
            start, end = pq[0][0], mx

        if elem_i + 1 < len(nums[list_i]):
            nxt = nums[list_i][elem_i + 1]
            heapq.heappush(pq, (nxt, list_i, elem_i + 1))
            mx = max(mx, nxt)

    return [start, end]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

C++:

vector<int> smallestRange(vector<vector<int>>& nums) {
    auto cmp = [](auto& a, auto& b) { return a[0] > b[0]; };
    priority_queue<vector<int>, vector<vector<int>>, decltype(cmp)> pq(cmp);
    int mx = INT_MIN, start = 0, end = INT_MAX;

    for (int i = 0; i < nums.size(); i++) {
        pq.push({nums[i][0], i, 0});
        mx = max(mx, nums[i][0]);
    }

    while (pq.size() == nums.size()) {
        auto cur = pq.top(); pq.pop();
        int mn = cur[0], li = cur[1], ei = cur[2];
        if (mx - mn < end - start) { start = mn; end = mx; }
        if (ei + 1 < nums[li].size()) {
            int nxt = nums[li][ei + 1];
            pq.push({nxt, li, ei + 1});
            mx = max(mx, nxt);
        }
    }
    return {start, end};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

复杂度:时间 O(NlogK),N 为元素总数,K 为列表数。空间 O(K)。

# 04. 与合并K个有序链表的对比

合并K链表 (B007) 最小区间 (K008)
数据结构 最小堆 最小堆
维护 — 额外维护 max
终止条件 堆空 某个列表耗尽
操作 取最小→接结果 取最小→更新区间→替换
复杂度 O(NlogK) O(NlogK)

# 05. 总结

核心启示 说明
多路归并堆 K 个有序列表的通用处理框架
窗口维护 min从堆中来,max手动跟踪
贪心滑动 每次弹出最小值,用同列表下一个替换
终止条件 任一列表耗尽即结束(无法再覆盖所有列表)

相关题目:B007 合并K个链表、F001 第K大元素

#算法
上次更新: 2026/06/17, 12:46:05
排序算法题合集
贪心算法题合集

← 排序算法题合集 贪心算法题合集→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式