
这道题是"合并两个有序链表"的终极加强版。
迭代
直接自底向上合并链表:
-
两两合并 :把 lists[0] 和 lists[1] 合并,合并后的链表保存在 lists[0] 中;把 lists[2] 和
lists[3] 合并,合并后的链表保存在 lists[2] 中;依此类推。
-
四四合并:把 lists[0] 和 lists[2] 合并(相当于合并前四条链表),合并后的链表保存在 lists[0] 中;把 lists[4] 和 lists[6] 合并,合并后的链表保存在 lists[4] 中;依此类推。
-
八八合并:把 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)。