leetcode hot100 23. 合并 K 个升序链表 hard 分治 迭代


这道题是"合并两个有序链表"的终极加强版。

迭代
直接自底向上合并链表:

  1. 两两合并 :把 lists0 和 lists1 合并,合并后的链表保存在 lists0 中;把 lists2 和

    lists3 合并,合并后的链表保存在 lists2 中;依此类推。

  2. 四四合并:把 lists0 和 lists2 合并(相当于合并前四条链表),合并后的链表保存在 lists0 中;把 lists4 和 lists6 合并,合并后的链表保存在 lists4 中;依此类推。

  3. 八八合并:把 lists0 和 lists4 合并(相当于合并前八条链表),合并后的链表保存在 lists0 中;把 lists8 和 lists12 合并,合并后的链表保存在 lists8 中;依此类推。

依此类推,直到所有链表都合并到 lists0 中。最后返回 lists0。


不需要创建新的数组,直接在原有的 lists 数组上进行原地合并。

  • 第一轮 (step=1):下标 0 和 1 合,2 和 3 合... 结果存回 0, 2, 4...
  • 第二轮 (step=2):下标 0 和 2 合,4 和 6 合... 结果存回 0, 4, 8...
  • 第三轮 (step=4):下标 0 和 4 合,8 和 12 合... 结果存回 0, 8...

举一个有 5 条链表的例子( n = 5 n=5 n=5)。初始状态:lists = L0, L1, L2, L3, L4

  • 第一轮 step = 1 :step * 2 = 2(每隔 2 个下标处理一对)。range(0, 5 - 1, 2) → \rightarrow → i 的取值只有 0 和 2。
    当 i = 0 时:合并 lists0 和 lists0 + 1。操作:lists0 = merge(L0, L1),step *= 2 =
    当 i = 2 时:合并 lists2 和 lists2 + 1。操作:lists2 = merge(L2, L3)
    结束循环, step =step ×2= 2
    lists 状态: (L0+L1), L1, (L2+L3), L3, L4 (注意:L1 和 L3 虽然还在,但我们再也不会去读它们了)
  • 第二轮 step = 2 : step * 2 = 4(每隔 4 个下标处理一对)。range(0, 5 - 2, 4) → \rightarrow → i 的取值只有 0。
    当 i = 0 时:合并 lists0 和 lists0 + 2。操作:lists0 = merge((L0+L1), (L2+L3))
    结束循环, step =step ×2= 4
    lists 状态: (L0+L1+L2+L3), L1, (L2+L3), L3, L4
  • 第三轮 step = 4 :step * 2 = 8。range(0, 5 - 4, 8) → \rightarrow → i 的取值依然只有 0。
    当 i = 0 时:合并 lists0 和 lists0 + 4。操作:lists0 = merge((L0+L1+L2+L3), L4)
    终于,落单的 L4 被合并进来了。
    结束循环,lists 状态: (L0+L1+L2+L3+L4), ...
python 复制代码
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:

    def mergeTwoLists(self, l1, l2):

        # 21. 合并两个有序链表
        # 创建一个哑节点,作为新链表的起点
        prehead = ListNode(-1)
        dummy =  prehead

        while l1 and l2:
            if l1.val <= l2.val:
                dummy.next = l1
                l1 = l1.next
            else:
                dummy.next = l2
                l2 = l2.next
            dummy = dummy.next
        
        # 有一个链表已经用完
        if l1:
            dummy.next = l1
        else:
            dummy.next = l2 
        # 两个都空不用挂

        return prehead.next


    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        
        if not lists:
            return None

        n = len(lists)
        step = 1
        
        # 当步长小于链表总数时,继续合并
        while step < n:
            # 将 lists[i + step] 合并到 lists[i] 中
            for i in range(0, n - step, step * 2):
                lists[i] = self.mergeTwoLists(lists[i], lists[i + step])     

            # 步长翻倍:1 -> 2 -> 4 -> 8
            step *= 2   

        return lists[0]                   

如果剩下的链表不足一对(落单了),它会自动保留到下一轮,直到遇到可以合并的对象。

时间复杂度 : O ( N log ⁡ K ) O(N \log K) O(NlogK)

外层 while 循环跑了 log ⁡ K \log K logK 次(步长不断翻倍)。由于步长翻倍(或规模减半),总共有 log ⁡ K \log K logK 层。

在每一层合并中,无论我们是怎么分组的(是 L 0 + L 1 L_0+L_1 L0+L1 还是 L 01 + L 23 L_{01}+L_{23} L01+L23),所有的 N N N 个节点都要通过 mergeTwoLists 重新排队。每一层都要处理 N N N 个节点。

总时间: N × log ⁡ K N \times \log K N×logK。

空间复杂度: O ( 1 ) O(1) O(1)

在原数组 lists 上操作。这是此解法的核心优势。

什么步长翻倍对应的是 log ⁡ K \log K logK 次循环

假设链表的总数 K K K,

循环停止的条件是 s t e p ≥ K step \ge K step≥K,每次循环 step = step × 2,即第 m m m 次循环后: s t e p = 2 m step = 2^m step=2m
2 m ≥ K 2^m \ge K 2m≥K根据对数的定义,对等式两边取对数: m ≥ log ⁡ 2 K m \ge \log_2 K m≥log2K

翻倍 、减半

  • 二分查找:每次搜索区间减半 → O ( log ⁡ n ) \rightarrow O(\log n) →O(logn)。
  • 分治合并:每次规模翻倍 → O ( log ⁡ n ) \rightarrow O(\log n) →O(logn)。
  • 快速幂:指数每次减半 → O ( log ⁡ n ) \rightarrow O(\log n) →O(logn)。
相关推荐
CoderYanger27 分钟前
A.每日一题:856. 括号的分数
java·程序人生·算法·leetcode·面试·职场和发展·学习方法
All for pursuit.1 小时前
【动态规划-8】152.乘积最大子数组
数据结构·c++·算法·leetcode·动态规划
Nil2082 小时前
leetcode 152乘积最大子数组
leetcode
夏幻灵13 小时前
LeetCode 21:合并两个有序链表——双指针与哑节点详解
算法·leetcode·链表
Navigator_Z15 小时前
LeetCode //C - 1286. Iterator for Combination
c语言·算法·leetcode
圣保罗的大教堂16 小时前
leetcode 301. 删除无效的括号 困难
leetcode
夏幻灵18 小时前
每日一题:LeetCode 206:反转链表——三指针与原地反转详解
算法·leetcode·链表
Tisfy21 小时前
LeetCode 0301.删除无效的括号:二进制枚举 / 回溯
算法·leetcode·字符串·题解·回溯·括号匹配·二进制枚举
Nil20821 小时前
leetcode 322零钱兑换
算法·leetcode·职场和发展
yi0111 天前
DAY24: LeetCode 167:两数之和 II|从暴力枚举到左右双指针
笔记·python·算法·leetcode·二分查找·双指针