Leetcode 链表 2
题号直达: 21 合并两个有序链表 | 142 环形链表 II | 2 两数相加 | 19 删除链表的倒数第 N 个结点 | 24 两两交换链表中的节点
LeetCode 21. 合并两个有序链表 ↑
题目
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
提示
- 两个链表的节点数目范围是 ([0, 50])
- (-100 ≤ Node.val ≤ 100)
list1和list2均按非递减顺序排列
示意图如下:

代码(Python)解法一:双指针原地拼接
class Solution(object):
def mergeTwoLists(self, list1, list2):
"""
:type list1: Optional[ListNode]
:type list2: Optional[ListNode]
:rtype: Optional[ListNode]
"""
if not list1:
return list2
if not list2:
return list1
p1 = list1
p2 = list2
if list1.val < list2.val:
p = list1
p1 = p1.next
else:
p = list2
p2 = p2.next
while p1 is not None and p2 is not None:
if p1.val < p2.val:
p.next = p1
p = p.next
p1 = p1.next
else:
p.next = p2
p = p.next
p2 = p2.next
if not p1:
while p2 is not None:
p.next = p2
p = p.next
p2 = p2.next
else:
while p1 is not None:
p.next = p1
p = p.next
p1 = p1.next
if list1.val < list2.val:
return list1
else:
return list2
思路解析(解法一)
- 双指针归并:维护两个指针
p1、p2分别指向list1、list2当前尚未合并的节点,每次从二者中选取较小值接到结果链表的尾部,并向前推进对应指针,类似归并排序中的「归并」步骤。 - 选择头结点:先比较
list1.val和list2.val,较小者作为最终结果链表的头结点p,同时把对应的p1/p2向前移动一位,后续在同一条链上继续追加。 - 处理剩余部分:当其中一条链表先走到末尾后,只需把另一条链表剩下的部分整体接到结果尾部即可(代码中通过两个
while补齐)。 - 返回值注意点:最后返回的头结点必须是最开始选出来的那个较小值结点,因此根据初始比较结果在末尾返回
list1或list2。 - 复杂度:每个节点只被访问一次并接到结果链表上,时间复杂度 (O(m + n)),其中 (m, n) 为两条链表长度;空间复杂度 (O(1)),仅使用了常数个指针,在原有链表节点上「原地重连」。
代码(Python)解法二:递归归并(写法更简洁)
class Solution(object):
def mergeTwoLists(self, list1, list2):
"""
:type list1: Optional[ListNode]
:type list2: Optional[ListNode]
:rtype: Optional[ListNode]
"""
if not list1:
return list2
elif not list2:
return list1
elif list1.val < list2.val:
list1.next = self.mergeTwoLists(list1.next, list2)
return list1
else:
list2.next = self.mergeTwoLists(list1, list2.next)
return list2
思路解析(解法二)
- 递归含义:
mergeTwoLists(list1, list2)的语义是「将list1和list2合并成一个有序链表并返回头结点」。在每一层递归中,从当前两个头结点中选出较小者作为本层的头结点,并递归地合并剩余部分。 - 状态转移:
- 若
list1为空,直接返回list2;若list2为空,直接返回list1; - 若
list1.val < list2.val,则list1应该排在前面,其余部分变为合并list1.next和list2的结果,即list1.next = mergeTwoLists(list1.next, list2); - 否则对称地处理
list2。
- 若
- 递归终止:当任一链表被耗尽(为空)时,返回另一条链表的剩余部分作为尾巴。
- 复杂度:每个节点仍然只会被访问一次并连接到结果链表上,时间复杂度 (O(m + n)),空间复杂度由于递归栈为 (O(m + n))。相比迭代写法更短、更接近题意,但在 Python 中要注意递归深度上限。
LeetCode 142. 环形链表 II ↑
题目
给定一个链表的头节点 head,返回链表开始入环的第一个节点。
如果链表无环,则返回 null。
评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。pos = -1 表示链表无环。注意:pos 仅用于标识环的实际情况,不作为函数参数传入。
提示
- 链表中节点数目在范围 ([0, 10^4]) 内
- (-10^5 ≤ Node.val ≤ 10^5)
pos的值为-1或者链表中的一个有效索引
进阶:你能否使用 (O(1))(即常量)内存解决此问题?
代码(Python)解法一:用列表记录访问过的节点,第一次重复即为入环点
class Solution(object):
def detectCycle(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head or not head.next:
return None
p = head
record = []
while p is not None:
if p in record:
return p
record.append(p)
p = p.next
return None
思路解析(解法一)
- 整体思路:从头指针
head出发顺着next一直向后遍历,同时维护一个容器record保存已经访问过的节点指针。遍历过程中:- 如果当前节点
p已经出现在record中,说明我们第二次到达了同一个节点,它正是环的入口(因为从入口开始再次回到自身);立即返回这个节点; - 否则就把
p加入record,继续向后走。
- 如果当前节点
- 无环情况:若最终遍历到
p is None,说明尾指针指向None,链表中不存在环,返回None。 - 与 141 题的区别:141 题只需要回答「有无环」,在第一次检测到重复节点时即可返回
True;本题则需要返回入环的节点本身,因此在检测到重复时返回p。 - 复杂度与不足:
- 时间复杂度:在当前实现中,
record使用列表存储,p in record为线性查找,最坏情况下时间复杂度接近 (O(n^2)); - 空间复杂度:需要 (O(n)) 额外空间保存访问过的节点指针;
- 若将
record改为set(),可以把查找均摊降到 (O(1)),整体时间复杂度为 (O(n)),但仍然没有达到进阶要求的 (O(1)) 额外空间;要实现常数空间,通常需要在快慢指针判环的基础上,再做一次指针“对齐”来精确定位环的入口。
- 时间复杂度:在当前实现中,
代码(Python)解法二:快慢指针 + 数学推导定位入环点
class Solution(object):
def detectCycle(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head or not head.next:
return None
slow = head # 乌龟节点
fast = head.next # 兔子节点
while fast is not None and fast.next is not None:
fast = fast.next.next
slow = slow.next
if fast == slow:
break
if fast is None or fast.next is None:
return None
slow = slow.next
ptr = head
while ptr is not None:
if ptr == slow:
return slow
ptr = ptr.next
slow = slow.next
思路解析(解法二)
- 先用快慢指针判断是否有环:和 141 题类似,用
slow每次走一步、fast每次走两步。如果最终fast或fast.next先走到None,说明无环,直接返回None;否则在某个时刻会有fast == slow,说明指针相遇于环内某一点。 - 为什么能找到入环点:设链表起点到入环点距离为 (a),入环点到相遇点距离为 (b),环长度为 (c)。可推出当快慢指针第一次相遇时:
- 慢指针走了 (a + b);
- 快指针走了 (a + b + k \cdot c)(多绕了若干圈);
- 又因为快指针速度是慢指针 2 倍,有 (2(a + b) = a + b + k \cdot c),从而得到 (a = k \cdot c - b)。
这说明从相遇点再走 (k \cdot c - b) 步就能回到入环点;而从链表头节点走 (a) 步也正好到入环点。
- 指针对齐定位入口:在代码中,让
slow从相遇点(这里先补走一步到真正的相遇位置)继续每次走一步,同时新指针ptr从head出发,每次走一步。根据上面的推导,当ptr == slow时,两者恰好在入环点相遇,即为所求。 - 复杂度与优势:时间复杂度 (O(n)),空间复杂度 (O(1)),只用了常数个指针变量,是满足进阶要求的标准做法;相比解法一不需要额外容器存储访问记录。
LeetCode 2. 两数相加 ↑
题目
给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例
- 输入:
l1 = [9, 9, 9, 9, 9, 9, 9],l2 = [9, 9, 9, 9] - 输出:
[8, 9, 9, 9, 0, 0, 0, 1](表示 9,999,999 + 9,999 = 10,009,998)
提示
- 每个链表中的节点数在范围 ([1, 100]) 内
- (0 ≤ Node.val ≤ 9)
- 题目数据保证列表表示的数字不含前导零(除了数字 0 本身)
代码(Python)解法一:分阶段模拟
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def addTwoNumbers(self, l1, l2):
"""
:type l1: Optional[ListNode]
:type l2: Optional[ListNode]
:rtype: Optional[ListNode]
"""
p = ListNode(0)
head = p
record = 0
while l1 and l2:
p.val = (l1.val + l2.val + record) % 10
record = (l1.val + l2.val + record) // 10
l1 = l1.next
l2 = l2.next
if l1 or l2:
p.next = ListNode(0)
p = p.next
if not l1:
while l2 != None:
p.val = (record + l2.val) % 10
record = (record + l2.val) // 10
if l2.next:
p.next = ListNode(0)
p = p.next
l2 = l2.next
else:
while l1 != None:
p.val = (record + l1.val) % 10
record = (record + l1.val) // 10
if l1.next:
p.next = ListNode(0)
p = p.next
l1 = l1.next
if record != 0:
p.next = ListNode(record)
return head
思路解析(解法一)
- 整体思路:模拟竖式加法,同时遍历两条链表,逐位相加并处理进位。用
record记录进位(0 或 1),每次(l1.val + l2.val + record) % 10得到当前位,// 10得到新的进位。 - 分阶段处理:先处理两条链表都未走完的部分(
while l1 and l2),再根据哪条链表更长,单独处理剩余部分;最后若仍有进位(record != 0),需要新建一个节点。 - 创建新链表:使用虚拟头
head和指针p逐步构建结果链表,每次需要继续时创建ListNode(0)并移动p。 - 复杂度:每个节点最多访问一次,时间复杂度 (O(\max(m, n))),空间复杂度 (O(\max(m, n))) 用于存储结果链表。
代码(Python)解法二:哑节点统一循环(更简洁)
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def addTwoNumbers(self, l1, l2):
"""
:type l1: Optional[ListNode]
:type l2: Optional[ListNode]
:rtype: Optional[ListNode]
"""
dummy = ListNode(0) # 创建哑节点
current = dummy
carry = 0 # 进位
while l1 or l2 or carry:
# 获取当前位的值
val1 = l1.val if l1 else 0
val2 = l2.val if l2 else 0
# 计算当前位的和及进位
total = val1 + val2 + carry
carry = total // 10
current.next = ListNode(total % 10)
# 移动指针
current = current.next
if l1:
l1 = l1.next
if l2:
l2 = l2.next
return dummy.next # 返回哑节点的下一个节点(真正的头节点)
思路解析(解法二)
- 本质相同:与解法一相同,均为模拟竖式加法的思路,逐位相加并处理进位。
- 哑节点 + 统一循环:使用哑节点
dummy后,只需一个while l1 or l2 or carry即可覆盖所有情况——两条链表都未走完、仅剩一条、以及最后进位,无需分阶段写三个分支。 - 写法更简洁:用
val1 = l1.val if l1 else 0处理空指针,逻辑更集中,代码更短。
LeetCode 19. 删除链表的倒数第 N 个结点 ↑
题目
给你一个链表,删除链表的倒数第 (n) 个结点,并且返回链表的头结点。
提示
- 链表中结点的数目为 (sz)
- (1 ≤ sz ≤ 30)
- (0 ≤ Node.val ≤ 100)
- (1 ≤ n ≤ sz)
进阶:你能尝试使用一趟扫描实现吗?
代码(Python)解法一:双指针一趟扫描
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def removeNthFromEnd(self, head, n):
"""
:type head: Optional[ListNode]
:type n: int
:rtype: Optional[ListNode]
"""
front = head
rear = head
for i in range(n):
front = front.next
if front == None:
return head.next
front = front.next
while front != None:
front = front.next
rear = rear.next
rear.next = rear.next.next
return head
思路解析(解法一)
- 双指针思路:用
front和rear两个指针,让front比rear超前 (n+1) 个节点。当front走到链表末尾(None)时,rear恰好在待删除节点的前一个,执行rear.next = rear.next.next即可删除。 - 先让 front 超前 n 步:
for i in range(n)后,front指向第 (n) 个节点(从 1 计数);再执行一次front = front.next,使front超前rear共 (n+1) 步。 - 删除头结点:若
front在第一次循环后即为None,说明链表长度为 (n),待删除的是头结点,直接返回head.next。 - 一趟扫描:满足进阶要求,时间复杂度 (O(sz)),空间复杂度 (O(1))。
代码(Python)解法二:递归(利用回溯得到倒序位置)
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def removeNthFromEnd(self, head, n):
"""
:type head: Optional[ListNode]
:type n: int
:rtype: Optional[ListNode]
"""
dummy = ListNode(0) # Java中的无参构造对应Python的默认值
dummy.next = head
self._remove(dummy, n)
return dummy.next
def _remove(self, head, n):
"""
:type head: ListNode
:type n: int
:rtype: int
"""
if head.next is None:
return 1
num = self._remove(head.next, n)
if num == n:
head.next = head.next.next
return num + 1
思路解析(解法二)
- 递归核心:
_remove(head, n)的返回值表示「从当前head到链表末尾共有多少个节点」。递归先一路到底(head.next is None时返回 1),回溯时num从尾到头递增,相当于从后往前数。 - 何时删除:当回溯到某一层时
num == n,说明当前head.next正是倒数第 (n) 个节点,执行head.next = head.next.next即可删除。 - 哑节点:在
head前加哑节点dummy,可统一处理删除头结点的情况,最后返回dummy.next。 - 规律总结:链表倒序相关的问题(如倒数第 k 个、从尾到头遍历等)总可以采用递归方法,利用递归回溯时「从尾到头」的顺序自然得到倒序信息。
- 复杂度:时间复杂度 (O(sz)),空间复杂度 (O(sz))(递归栈深度)。
LeetCode 24. 两两交换链表中的节点 ↑
题目
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
提示
- 链表中节点的数目在范围 ([0, 100]) 内
- (0 ≤ Node.val ≤ 100)
代码(Python)解法一:递归
class Solution(object):
def swapPairs(self, head):
"""
:type head: Optional[ListNode]
:rtype: Optional[ListNode]
"""
if head == None or head.next == None:
return head
newHead = head.next
head.next = self.swapPairs(newHead.next)
newHead.next = head
return newHead
思路解析(解法一)
- 递归含义:
swapPairs(head)表示「对以head为头的链表进行两两交换,并返回交换后的新头结点」。 - 终止条件:若链表为空或只有一个节点,无需交换,直接返回
head。 - 交换逻辑:设当前两个节点为
head和newHead = head.next。先递归处理newHead.next之后的子链表,得到交换后的子链表头;再将head接到子链表后面,newHead接到head前面,即完成本对交换;最后返回newHead作为本段的新头。 - 复杂度:每个节点访问一次,时间复杂度 (O(n)),空间复杂度 (O(n))(递归栈)。
代码(Python)解法二:迭代(哑节点 + 指针调整)
class Solution(object):
def swapPairs(self, head):
"""
:type head: Optional[ListNode]
:rtype: Optional[ListNode]
"""
dummyHead = ListNode(0)
dummyHead.next = head
temp = dummyHead
while temp.next != None and temp.next.next != None:
node1 = temp.next
node2 = temp.next.next
temp.next = node2
node1.next = node2.next
node2.next = node1
temp = node1
ans = dummyHead.next
del dummyHead
return ans
思路解析(解法二)
- 哑节点:在
head前加dummyHead,使交换第一对时逻辑与后续对一致,无需单独处理头结点。 - 迭代逻辑:
temp指向「当前待交换 pair 的前驱」。每次循环中,node1、node2为待交换的两个节点;执行temp.next = node2、node1.next = node2.next、node2.next = node1完成交换;再将temp移到node1(交换后 pair 的尾结点),作为下一对的前驱。 - 终止条件:当
temp.next或temp.next.next为None时,不足一对,退出循环。 - 复杂度:时间复杂度 (O(n)),空间复杂度 (O(1)),满足进阶要求。

浙公网安备 33010602011771号