合并K个升序链表是一道常考题。
下面分析如何解题。
设每个链表的平均长度是a。设所有链表的节点数之和是n,已知n=k*a。
本文给出三种方法
- 整体排序。
- 小顶堆。
- 分治思想。
解法一
整体排序。
代码已提交通过。
import "sort"
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func mergeKLists(lists []*ListNode) *ListNode {
if len(lists) == 0 {
return nil
}
var nodes []*ListNode
// 链表节点都加入数组
for _, list := range lists {
curr := list
for curr != nil {
nodes = append(nodes, curr)
curr = curr.Next
}
}
sort.Sort(Nodes(nodes)) // 数组排序
var dummy ListNode
curr := &dummy
// 拼接出新链表
for _, node := range nodes {
curr.Next = node
curr = node
}
// 注意尾节点的Next指针一定要置nil。
// 因为使用的排序算法可能是“不稳定”的,
// 所以排序完成后,最后一个节点的Next指针可能不是nil。
curr.Next = nil
return dummy.Next
}
type Nodes []*ListNode
func (o Nodes) Len() int {
return len(o)
}
func (o Nodes) Swap(i, j int) {
o[i], o[j] = o[j], o[i]
}
func (o Nodes) Less(i, j int) bool {
return o[i].Val < o[j].Val
}
遍历每个链表,把节点都放进数组,时间复杂度;数组排序最优时间复杂度是。所以整体上时间复杂度是。
因为需要一个数组存储所有节点,空间复杂度。
解法二
基于小顶堆。重点在MinHeap的Pop方法。
代码已提交通过。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func mergeKLists(lists []*ListNode) *ListNode {
l := len(lists)
if l == 0 {
return nil
}
h := NewMinHeap(l)
// 把每个链表的头节点都压入堆
for _, list := range lists {
h.Push(list)
}
var dummy ListNode
curr := &dummy
// 不断弹出堆顶,并串接到新链表尾部
for !h.IsEmpty() {
node := h.Pop()
curr.Next = node
curr = node
}
return dummy.Next
}
// 小顶堆
type MinHeap struct {
data []*ListNode // 存储数据
size int // data中有效元素的个数
}
// 创建堆对象
// maxLen需要的最大存储空间大小
func NewMinHeap(maxLen int) *MinHeap {
return &MinHeap{
data: make([]*ListNode, maxLen),
}
}
func (h *MinHeap) IsEmpty() bool {
return h.size == 0
}
// 压入数据
func (h *MinHeap) Push(node *ListNode) {
// 注意检查空指针
if node == nil {
return
}
h.data[h.size] = node
h.size++
h.siftUp()
}
// 从堆最后一个位置向上调整
func (h *MinHeap) siftUp() {
if h.size < 2 {
return
}
child := h.size - 1
childV := h.data[child]
for child > 0 {
p := (child-1)/2
pv := h.data[p]
if pv.Val <= childV.Val {
break
}
h.data[child] = pv
child = p
}
h.data[child] = childV
}
// 弹出堆顶
func (h *MinHeap) Pop() *ListNode {
if h.IsEmpty() {
panic("MinHeap is empty")
}
node := h.data[0]
next := node.Next
if next != nil { // 堆顶节点在其链表中的直接后继非空
h.data[0] = next // 修改堆顶,堆大小不变
h.siftDown()
} else { // 堆顶节点在其链表中的直接后继为空,说明那个链表已经处理完了
if h.size > 1 {
h.data[0] = h.data[h.size-1] // 堆最后一个元素覆盖堆顶
h.size-- // 减小堆大小
h.siftDown()
} else {
h.size--
}
}
return node
}
// 从堆顶向下调整
func (h *MinHeap) siftDown() {
if h.size < 2 {
return
}
var p int
pv := h.data[p]
for {
child := 2*p + 1
if child >= h.size {
break
}
if child+1 < h.size && h.data[child+1].Val < h.data[child].Val {
child++
}
if pv.Val <= h.data[child].Val {
break
}
h.data[p] = h.data[child]
p = child
}
h.data[p] = pv
}
每次堆调整的时间复杂度。所以整体上时间复杂度。
堆最多存储k个节点,空间复杂度。
解法三
利用分治思想。具体方法是从底向上合并。
代码已提交通过。
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func mergeKLists(lists []*ListNode) *ListNode {
l := len(lists)
if l == 0 {
return nil
}
// lists首尾两两合并,迭代这个过程。
// 下面注释内容只是示意,请结合代码理解具体操作。
// 比如lists是这样的 [ l1, l2, l3, l4, l5, l6, l7 ],
// 一次合并后 [ (l1;l7), (l2;l6), (l3;l5), l4 ],
// 再次合并后 [ (l1;l7;l4), (l2;l6;l3;l5) ],
// 再次合并后 [ (l1;l7;l4;l2;l6;l3;l5) ],合并完成。
for i, j := 0, l-1; j > 0; i = 0 { // 注意进入内层循环之前i置为0
for ; i < j; i, j = i+1, j-1 {
lists[i] = mergeTwoLists(lists[i], lists[j]) // 复用lists[i]
}
}
return lists[0]
}
// 按节点值从小到大的顺序合并两个链表
func mergeTwoLists(l1, l2 *ListNode) *ListNode {
var (
i1 = l1
i2 = l2
dummy ListNode
curr = &dummy
)
for i1 != nil && i2 != nil {
if i1.Val <= i2.Val {
curr.Next = i1
curr = i1
i1 = i1.Next
} else {
curr.Next = i2
curr = i2
i2 = i2.Next
}
}
if i1 != nil { // l1有剩余,串接它
curr.Next = i1
}
if i2 != nil { // l2有剩余,串接它
curr.Next = i2
}
return dummy.Next
}
分析mergeKLists函数的两层for循环。内层循环从进入循环到退出循环,每个节点处理一遍,所以时间复杂度是。外层循环后一次执行循环体的 j 都变成前一次的一半,可知外层循环的次数是。所以整体时间复杂度。
从代码可见,空间复杂度。