1. 合并 K 个有序链表中逐一合并、分治合并、小顶堆三种解法的复杂度与工程取舍?
对比逐一合并、分治合并、小顶堆三种合并 K 个有序链表的方法的复杂度与工程取舍?
- 三种方法的时间/空间复杂度
- 逐一合并 O(K²N) 与分治/堆 O(KN log K) 的差异
- 工程取舍(代码复杂度、稳定性、常数)
设链表总节点数 N、K 个链表。逐一合并:每次合并两个链表 O(N),合并 K 次,但链会越来越长,总 O(K²·L)(L 为平均链长),若用 N 表示总节点则 O(KN)。分治合并:两两合并成对,再逐层合并,每层处理全部 N 个节点、共 log K 层,总 O(N log K)。小顶堆:把 K 个表头入堆,每次弹出最小后把其 next 入堆,共 N 次弹出、每次 O(log K),总 O(N log K),空间 O(K)。工程取舍:堆实现最直观且空间 O(K)、写起来短;分治无需额外堆空间(递归栈 O(log K))且对"链表本身不可变"更友好;逐一合并简单但慢,仅当 K 很小用。三者都稳定(相等值按链表顺序)。
复杂度差异根源:逐一合并每轮都从 0 增长,导致 O(KN);分治与堆都把"取最小"的代价降到 O(log K)。堆是"多路归并"的自然实现,分治是"二分归并"的推广。工程上堆最常用,分治在注重额外空间时更优。
// 小顶堆解法
ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
for (ListNode l : lists) if (l != null) pq.offer(l);
ListNode dummy = new ListNode(0), cur = dummy;
while (!pq.isEmpty()) {
ListNode node = pq.poll();
cur.next = node; cur = cur.next;
if (node.next != null) pq.offer(node.next);
}
return dummy.next;
}