《剑指 Offer》学习记录:题 25:合并两个排序的链表

题 25:合并两个排序的链表

题干

输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。——《剑指 Offer》P145

测试样例

链表的数据结构定义如下(Python):

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

若传入如下 2 个有序的链表:

则 2 个链表合并后,得到的链表也是要有序的:

二路归并

方法思路

有序表的二路归并,思路还是比较清晰的。由于 2 个链表本身是有序的,因此只需要依次从 2 张表中选择适合的元素,用尾插法插入新表即可。例如对于如下结构,指针 ptr1 指向第一个链表的首元结点,指针 ptr2 指向第一个链表的首元结点,开辟一个头结点 head 作为新链表。

由于新链表为升序,因此考查 ptr1 和 ptr2 指向的结点,ptr1 指向的结点加入 head。head 插入新结点之后,需要将 ptr1 向后移动。

ptr1 和 ptr2 指向的结点,ptr2 指向的结点的 val 较小所以加入 head。head 插入新结点之后,需要将 ptr2 向后移动。

重复上述操作,依次将结点都加入到新链表。

由于此时 ptr1 已经指向表尾,也就是说链表 1 的结点都加入新链表了。对于链表 2 剩余的元素,由于 2 个链表本身都是有序的,因此可以直接将剩余结点加入新链表。

题解代码

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        ptr = head = ListNode(0)    #新链表的头结点
        while l1 and l2:    #考查 2 张链表的接点,依次加入新链表
            if l1.val <= l2.val:
                ptr.next = l1    
                l1 = l1.next
            else:
                ptr.next = l2
                l2 = l2.next
            ptr = ptr.next

        if l1 != None:
            ptr.next = l1    #链表 1 的剩余结点加入新链表
        else:
            ptr.next = l2    #链表 2 的剩余结点加入新链表
        
        return head.next

时空复杂度

设链表 1 的表长为 m,链表 2 的表长为 n。由于需要遍历 2 张链表,因此时间复杂度为 O(m + n)。
将 2 个有序链表归并为一张链表不需要额外空间,因此空间复杂度为 O(1)。

递归法

方法思路

递归法相对比较巧妙,递归层次在的是对于新链表的结点,递归函数的返回值是递归层次所在结点的后继。递归的出口是还未全部加入新链表的剩余结点,例如如图所示情况,结点 6 是递归访问链表 1 后,链表 2 的剩余结点,这个是递归的出口开始回溯。返回的上一个递归层次是结点 5,让结点 5 的后继为递归函数的返回值。

再次回溯,结点 5 作为递归函数的返回值,当前递归层次所在的结点是 4。不断向上回溯,最后第一个访问的结点将会指向归并后的链表结点。

题解代码

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        if l1 == None:
            return l2    #链表 1 访问完毕,返回剩余链表 2 剩余结点
        elif l2 == None:
            return l1    #链表 2 访问完毕,返回剩余链表 1 剩余结点
        
        if l1.val <= l2.val:
            l1.next = self.mergeTwoLists(l1.next, l2)
            return l1
        else:
            l2.next = self.mergeTwoLists(l1, l2.next)
            return l2

时空复杂度

设链表 1 的表长为 m,链表 2 的表长为 n。由于需要访问 2 张链表的所有结点,因此时间复杂度为 O(m + n)。
由于递归需要占用额外的空间保存状态,因此空间复杂度为 O(n)。

参考资料

《剑指 Offer(第2版)》,何海涛 著,电子工业出版社
【合并两个排序的链表】:迭代,递归

posted @ 2021-04-18 23:19  乌漆WhiteMoon  阅读(90)  评论(0编辑  收藏  举报