《剑指 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版)》,何海涛 著,电子工业出版社
【合并两个排序的链表】:迭代,递归