【LeetCode】23. 合并 K 个升序链表

30 篇文章 4 订阅

题目链接:23. 合并 K 个升序链表
题目描述:
在这里插入图片描述
数据范围:
在这里插入图片描述

**思考:**这题实际上就是合并两个有序列表的进阶版,只不过这里变成了合并K个,那么这里我们显然就知道,核心的合并两个有序列表的思路不变,剩下的重点处理就在于如何将这K个链表进行两两合并了,方式有很多,但效率不一,下面介绍几种易想到的思路:

方法一:顺序合并

顺序合并思路很简单,就是顺序地将这K个链表两两地进行合并。

代码:

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func mergeKLists(lists []*ListNode) *ListNode {
    if len(lists) == 0 {
        return new(ListNode).Next
    }

    res := lists[0]
    lists = lists[1:]
    for _,list := range lists {
        res = mergeTwoLists(res,list)
    }

    return res
}
// 合并两个升序链表
func mergeTwoLists(l1 ,l2 *ListNode) *ListNode {
    head := new(ListNode)
    l := head
    for ;l1 != nil && l2 != nil; {
        if l1.Val < l2.Val {
            l.Next = l1
            l1 = l1.Next
        }else {
            l.Next = l2
            l2 = l2.Next
        }
        l = l.Next
    }

    if l1 != nil {
        l.Next = l1
    }
    if l2 != nil {
        l.Next = l2
    }

    return head.Next
}

在这里插入图片描述

方法二、分治

顺序合并的效率并不高,这样做就类似于阻塞操作,合并前面的链表的时候,无关的链表啥事儿都干不了,因此,我们可以考虑进行分治,先递归地划分区间两两合并,最后再将总的合并起来。

代码:

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func mergeKLists(lists []*ListNode) *ListNode {
    if len(lists) == 0 {
        return new(ListNode).Next
    }
    return merge(0,len(lists)-1,lists)
}

func merge(l,r int,lists []*ListNode) *ListNode {
    if l > r {
        return nil
    }
    if l == r {
        return lists[l]
    }
    mid := (l+r)>>1
    
    return mergeTwoLists(merge(l,mid,lists),merge(mid+1,r,lists))
}

func mergeTwoLists(l1 ,l2 *ListNode) *ListNode {
    head := new(ListNode)
    l := head
    for ;l1 != nil && l2 != nil; {
        if l1.Val < l2.Val {
            l.Next = l1
            l1 = l1.Next
        }else {
            l.Next = l2
            l2 = l2.Next
        }
        l = l.Next
    }

    if l1 != nil {
        l.Next = l1
    }
    if l2 != nil {
        l.Next = l2
    }

    return head.Next
}

在这里插入图片描述

方法三、小根堆

看了下题解找出了不同的写法的,基本上用了小根堆(优先队列)的结构来实现的,思路就是初始时将每个链表的头结点加入到堆中,调整成为一个小根堆,那么堆顶结点一定是最小的。循环取堆中的元素,直到堆为空,注意,每次从堆中取出一个节点就需要将该节点从堆中移除,并将这个节点的下一个节点加入到堆中。

代码:

func mergeKLists(lists []*ListNode) *ListNode {
    h := hp{}
    for _, head := range lists {
        if head != nil {
            h = append(h, head)
        }
    }
    heap.Init(&h) // 初始化小根堆

    res := &ListNode{} // 哨兵节点,作为合并后链表头节点的前一个节点
    cur := res // 当前合并的链表位置,也就res链表末尾
    for len(h) > 0 {
        node := heap.Pop(&h).(*ListNode) // 取出堆顶元素
        if node.Next != nil { // 该节点的下一个节点不空,就再加入堆中
            heap.Push(&h, node.Next)
        }
        cur.Next = node // 记录到答案中
        cur = cur.Next // 准备合并下一个节点
    }
    return res.Next
}

// golang中小根堆的实现
type hp []*ListNode
func (h hp) Len() int           { return len(h) }
func (h hp) Less(i, j int) bool { return h[i].Val < h[j].Val } // 最小堆
func (h hp) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
func (h *hp) Push(x interface{}) {*h = append(*h, x.(*ListNode))}
func (h *hp) Pop() interface{} {
    n := len(*h)
    ans := (*h)[n-1] // n-1个元素就是堆顶元素
    *h = (*h)[:n-1]
    return ans
}

在这里插入图片描述
这种做法很容易能看出复杂度为O(n*logk),其中k是链表长度,而n是所有链表节点数之和,这里logk主要是k个链表加入到堆中,所以时间复杂度为logk。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

童话ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值