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


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

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

  1. 两两合并 :把 lists[0] 和 lists[1] 合并,合并后的链表保存在 lists[0] 中;把 lists[2] 和

    lists[3] 合并,合并后的链表保存在 lists[2] 中;依此类推。

  2. 四四合并:把 lists[0] 和 lists[2] 合并(相当于合并前四条链表),合并后的链表保存在 lists[0] 中;把 lists[4] 和 lists[6] 合并,合并后的链表保存在 lists[4] 中;依此类推。

  3. 八八合并:把 lists[0] 和 lists[4] 合并(相当于合并前八条链表),合并后的链表保存在 lists[0] 中;把 lists[8] 和 lists[12] 合并,合并后的链表保存在 lists[8] 中;依此类推。

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


不需要创建新的数组,直接在原有的 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 时:合并 lists[0] 和 lists[0 + 1]。操作:lists[0] = merge(L0, L1),step *= 2 =
    当 i = 2 时:合并 lists[2] 和 lists[2 + 1]。操作:lists[2] = 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 时:合并 lists[0] 和 lists[0 + 2]。操作:lists[0] = 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 时:合并 lists[0] 和 lists[0 + 4]。操作:lists[0] = 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)。
相关推荐
期末考复习中,蓝桥杯都没时间学了2 小时前
力扣刷题13
数据结构·算法·leetcode
会飞的战斗鸡3 小时前
JS中的链表(含leetcode例题)
javascript·leetcode·链表
多米Domi0113 小时前
0x3f 第48天 面向实习的八股背诵第五天 + 堆一题 背了JUC的题,java.util.Concurrency
开发语言·数据结构·python·算法·leetcode·面试
渐暖°3 小时前
【leetcode算法从入门到精通】5. 最长回文子串
vscode·算法·leetcode
今天_也很困3 小时前
LeetCode热题100-560. 和为 K 的子数组
java·算法·leetcode
v_for_van3 小时前
力扣刷题记录2(无算法背景,纯C语言)
c语言·算法·leetcode
alphaTao4 小时前
LeetCode 每日一题 2026/1/26-2026/2/1
算法·leetcode
We་ct4 小时前
LeetCode 54. 螺旋矩阵:两种解法吃透顺时针遍历逻辑
前端·算法·leetcode·矩阵·typescript
We་ct6 小时前
LeetCode 36. 有效的数独:Set实现哈希表最优解
前端·算法·leetcode·typescript·散列表