题目描述:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = [] 输出:[]
示例 3:
输入:l1 = [], l2 = [0] 输出:[0]
提示:
- 两个链表的节点数目范围是
[0, 50] -100 <= Node.val <= 100l1和l2均按 非递减顺序 排列
前置python链表知识
节点定义
pythonclass ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next注意
__init__是前后各两个下划线 ,写成_init_会报错。
从数组建链表
python
def build_list(nums):
dummy = ListNode()
cur = dummy
for x in nums:
cur.next = ListNode(x)
cur = cur.next
return dummy.next
遍历
python
cur = head
while cur:
print(cur.val)
cur = cur.next
哑节点 dummy
避免处理头节点被删/插入的特殊情况,最后返回 dummy.next。
python
dummy = ListNode(0, head)
cur = dummy
# 操作 cur.next
return dummy.next
判断链表为空:
python
if not head: # 空链表(head 是 None)
if head is None: # 等价写法
判断不为空:
python
if head:
if head is not None:
遍历到末尾:
python
while cur: # cur 不为 None 就继续
cur = cur.next
判断尾节点:
python
if not cur.next: # 下一个是 None,说明是尾节点
本题解法1:
python
class ListNode:
def __init__(self, val=0, next=None):
self.val = val # 节点存的值
self.next = next # 指向下一个节点,默认 None
class Solution:
def mergeTwoLists(self, list1, list2):
dummy = ListNode(-1) # 造一个哑节点,值随便给(-1),只用来占位
cur = dummy # cur 是"结果链表的尾巴",初始指向 dummy
while list1 and list2: # 两个链表都还有节点时,循环比较
if list1.val <= list2.val:
cur.next = list1 # list1 的当前节点更小(或相等),接到结果尾部
list1 = list1.next # list1 往后走一步
else:
cur.next = list2 # 否则接 list2 的当前节点
list2 = list2.next # list2 往后走一步
cur = cur.next # 结果尾部指针往后走一步
# 循环结束时,必有一个链表走完了
# 把还没走完的那个链表整段接上(它本身有序,无需再逐个比)
cur.next = list1 if list1 else list2
return dummy.next # dummy 是占位的,真正的头是 dummy.next
两个链表都是升序的,那结果也应该是升序的。所以想到用两个指针,每次比较当前两个节点的值,谁小就先把谁接上。这样从头到尾走一遍,出来的就是有序的。
问题是:结果链表一开始是空的,头节点怎么处理?如果不用技巧,就要单独判断"结果为空"的情况,很麻烦。后来想到用哑节点 dummy,让它当"假头",真正的头是 dummy.next,这样就统一了。
复杂度分析:时间复杂度是 O(m+n),其中 m、n 分别是两个链表的长度------因为 while 循环每次比较都会"消费"掉一个节点,最坏情况下要把两个链表的全部节点各走一遍,每次循环内部只是比较和改指针,都是常数操作,所以总时间与两链表长度之和成正比;空间复杂度是 O(1),因为只新建了一个哑节点 dummy 和 cur、list1、list2 几个固定数量的指针变量,没有开数组、没有递归、也没有 new 新节点,原链表的节点都是原地复用,只是把 next 指针重新串起来,所以额外空间是常数级。
解法2:
python
class Solution(object):
def mergeTwoLists(self, list1, list2):
# 出口1:list1 空了,剩下 list2 本身就是有序的,直接还回去
if not list1:
return list2
# 出口2:list2 空了,剩下 list1 直接还回去
if not list2:
return list1
# 比较两个链表当前头节点的值,决定谁当合并后的头
if list1.val <= list2.val:
# list1 的头更小,所以它当这一层的头
# list1 后面接什么?接"list1 剩下的部分"和"list2"合并的结果
# 这里递归调用自己,把子问题丢给函数,返回值是拼好的后半段
list1.next = self.mergeTwoLists(list1.next, list2)
# 把拼好的整条链表交回给上一层
return list1
else:
# list2 的头更小,它当这一层的头
# list2 后面接"list1"和"list2 剩下的部分"合并的结果
list2.next = self.mergeTwoLists(list1, list2.next)
# 交回给上一层
return list2
这道题是链表递归的入门经典,核心思想只有一句话:只看当前这一步,剩下的交给函数自己 。递归三要素在这里体现得非常清楚:出口 是任意一个链表为空时直接返回另一个(因为它本身有序,不用再处理);当前这一步 是比较两个头节点的值,谁小谁当这一层结果的头;递归 则是把"头后面该接什么"这个问题原封不动丢给函数自己,也就是 list1.next = mergeTwoLists(list1.next, list2)。很多人第一次想不出来,是因为习惯了迭代"从头往后一个个接"的思路,而递归是反着来的------先一路往下钻到出口,再在返回的路上,一层层把当前节点接到下层返回的整条链表前面,每一层返回的都是一条已经拼好的完整链表,最后自然就串成了答案。代码只有短短十行,却把"最小子问题 + 递归调用 + 组合结果"的套路演绎得很干净,非常适合拿来理解递归的思维方式;不过要注意递归的空间复杂度是 O(m+n)(调用栈),链表很长时会爆栈,所以工程上更推荐迭代写法------递归用来理解思路,迭代用来实际提交,这是刷链表题的一个实用原则。