Leetcode 链表 1
题号直达: 160 相交链表 | 206 反转链表 | 234 回文链表 | 141 环形链表
LeetCode 160. 相交链表 ↑
题目
给你两个单链表的头节点 headA 和 headB,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null。
题目保证整个链式结构中不出现环。
示意图如下:

提示
- listA 中节点数目为 m
- listB 中节点数目为 n
- 1 ≤ m, n ≤ 3 × 10⁴
- 1 ≤ Node.val ≤ 10⁵
- 0 ≤ skipA ≤ m
- 0 ≤ skipB ≤ n
- 如果 listA 和 listB 没有交点,
intersectVal为 0 - 如果 listA 和 listB 有交点,
intersectVal == listA[skipA] == listB[skipB]
进阶:你能否设计一个时间复杂度 O(m + n)、仅用 O(1) 内存的方案?
代码(Python)解法一:哈希集合记录访问过的节点
class Solution(object):
def getIntersectionNode(self, headA, headB):
"""
:type headA: ListNode
:type headB: ListNode
:rtype: ListNode
"""
visited = set()
temp = headA
while temp:
visited.add(temp)
temp = temp.next
temp = headB
while temp:
if temp in visited:
return temp
temp = temp.next
return None
思路解析(解法一)
- 核心想法:先遍历链表 A,把遇到的每个节点指针都丢进集合
visited;再遍历链表 B,一边走一边判断当前节点是否已经在集合中,若在则说明这是两个链表第一个相交的节点。 - 正确性:相交意味着从某个节点开始,接下来的 next 指针完全共享,因此一旦在 B 的遍历过程中遇到集合中已有的节点,这个节点必然是相交起点。
- 复杂度:时间复杂度 O(m + n),空间复杂度 O(m)(集合存储链表 A 的节点)。满足时间复杂度要求,但没有做到进阶中的 O(1) 额外空间(可以作为后续双指针解法的对比基线)。
代码(Python)解法二:双指针「对齐长度」法(O(1) 空间)
class Solution(object):
def getIntersectionNode(self, headA, headB):
"""
:type headA: ListNode
:type headB: ListNode
:rtype: ListNode
"""
# 如果任一链表为空,不可能相交
if not headA or not headB:
return None
# 双指针初始化
pA, pB = headA, headB
# 当两个指针不相遇时继续移动
while pA != pB:
# pA 移动:如果到达末尾,转到 headB;否则继续向前
if pA:
pA = pA.next
else:
pA = headB
# pB 移动:如果到达末尾,转到 headA;否则继续向前
if pB:
pB = pB.next
else:
pB = headA
# 返回相遇点(可能是交点,也可能是 None)
return pA
思路解析(解法二)
- 长度对齐的巧妙写法:指针
pA先走完链表 A 再接链表 B,pB先走完链表 B 再接链表 A,它们总共都走了lenA + lenB步。如果有交点,走到第二轮时会在交点处相遇;如果没有交点,则最终都变为None,也在同一位置相遇。 - 为什么一定会相遇:第一轮结束时,
pA比pB多走了lenA - lenB或反之,通过第二轮互换起点,这部分差距被刚好抵消,因此两者会在同一时刻到达交点(或同时为None)。 - 复杂度:时间复杂度 O(m + n),空间复杂度 O(1),完全满足进阶要求,也是本题最常用、最优雅的写法之一。
LeetCode 206. 反转链表 ↑
题目
给你单链表的头节点 head,请你反转链表,并返回反转后的链表。
提示
- 链表中节点数目范围是 ([0, 5000])
- (-5000 ≤ Node.val ≤ 5000)
进阶:链表可以使用迭代或递归方式完成反转,你能否用两种方式都写一写?
代码(Python)解法一:用数组记录节点,再倒序重连
class Solution(object):
def reverseList(self, head):
"""
:type head: Optional[ListNode]
:rtype: Optional[ListNode]
"""
if not head:
return None
record = []
p = head
while p:
record.append(p)
p = p.next
ans = record[-1]
temp = ans
for i in range(1, len(record)):
temp.next = record[len(record) - i - 1]
temp = temp.next
temp.next = None
return ans
思路解析(解法一)
- 整体思路:先顺序遍历链表,把每个节点指针依次存入数组
record;然后从数组最后一个节点开始,按倒序把next指针重新连起来,相当于把链表反向“串”回去。 - 注意点 1:空链表:函数开头先判断
if not head: return None,避免后面对空指针取next或访问record[-1]时出错。 - 注意点 2:尾结点要指向
None:重建完倒序链表后,最后一个节点的next仍然保留着原链表中的旧指向,必须显式设为None(temp.next = None),否则会形成错误的链表结构,甚至可能产生环。 - 复杂度:遍历一次构建数组,再遍历一次数组倒序连边,时间复杂度 (O(n)),需要 (O(n)) 额外空间保存所有节点指针;符合题目时间要求,但没有做到原地 (O(1)) 空间,后续可以用原地迭代指针的写法优化空间。
代码(Python)解法二:递归反转(从尾到头“归”回来)
class Solution(object):
def reverseList(self, head):
"""
:type head: Optional[ListNode]
:rtype: Optional[ListNode]
"""
# 基本情况:空链表或只有一个节点
if not head or not head.next:
return head
# 递归反转后续链表
newHead = self.reverseList(head.next)
# 将当前节点的下一个节点的 next 指向当前节点
head.next.next = head
# 将当前节点的 next 设为 None
head.next = None
return newHead
思路解析(解法二)
- 递归含义:
reverseList(head)的语义是「反转从head开始的链表,并返回反转后的新头结点」。对当前节点head,先递归处理head.next,让newHead指向反转后的子链表头。 - 指针翻转:当递归从最深处往回“归”时,
head.next已经是反转后子链表的尾部之一,此时把head.next.next = head,相当于把当前节点挂到子链表尾部;再把head.next = None断开原来的正向链接,避免成环。 - 终止条件:当遇到空链表或只有一个节点时直接返回
head,这一层作为最终的newHead一路返回到最外层。 - 复杂度与优缺点:时间复杂度同样是 (O(n)),空间复杂度由于递归栈为 (O(n))。写法简洁、逻辑清晰,但要注意递归深度上限;在 Python 中节点数上限 (5000) 仍在默认递归深度允许范围内。
LeetCode 234. 回文链表 ↑
题目
给你一个单链表的头节点 head,请你判断该链表是否为回文链表。如果是,返回 true;否则,返回 false。
提示
- 链表中节点数目在范围 ([1, 10^5]) 内
- (0 ≤ Node.val ≤ 9)
进阶:你能否用 (O(n)) 时间复杂度和 (O(1)) 空间复杂度解决此题?
代码(Python)解法一:把值拷贝到数组里做双指针
class Solution(object):
def isPalindrome(self, head):
"""
:type head: Optional[ListNode]
:rtype: bool
"""
if not head:
return True
record = []
p = head
while p != None:
record.append(p.val)
p = p.next
N = len(record) // 2
for i in range(N):
if record[i] != record[len(record) - 1 - i]:
return False
return True
思路解析(解法一)
- 转化为数组回文判断:顺序遍历链表,把每个节点的值依次压入数组
record。这样链表是否回文就等价于数组record是否回文。 - 双指针比较:只需检查前一半元素是否和后一半对称相等:对每个
i ∈ [0, N-1],判断record[i]是否等于record[len(record) - 1 - i],一旦有一对不相等就返回False,全部通过则是回文。 - 边界情况:当链表只有 0 或 1 个节点时,一定是回文,代码中
if not head: return True覆盖了空链表;长度为 1 时N = len(record) // 2为 0,不会进入循环,也会返回True。 - 复杂度与不足:时间复杂度 (O(n)),空间复杂度 (O(n))(需要额外数组保存所有节点的值),实现简单直观;但没有达到进阶要求的 (O(1)) 额外空间,后续可以通过「反转后半段链表 + 原地比较」来优化空间。
代码(Python)解法二:递归回溯 + 前后双指针
class Solution(object):
def isPalindrome(self, head):
self.front = head
def check(node):
if node:
if not check(node.next):
return False
if node.val != self.front.val:
return False
self.front = self.front.next
return True
return check(head)
思路解析(解法二)
- 递归当作「从右往左」的指针:
recursivelyCheck(currentNode)先一路递归到链表尾部,等递归开始回溯时,currentNode会从尾结点开始不断往前走,相当于得到一个「从右往左」的遍历顺序。 - 前后双指针比较:用成员变量
self.frontPointer作为从左往右的指针,每次在回溯阶段比较currentNode.val与self.frontPointer.val,若不相等直接返回False;相等则把self.frontPointer向前挪一步,继续回溯上一层。 - 终止与短路:如果某一层已经返回
False,上层递归直接短路返回False;若所有层都通过比较,则最终返回True。 - 复杂度:时间复杂度仍为 (O(n)),需要 (O(n)) 的递归栈空间;相比数组法节省了显式的数组存储,但仍不满足进阶的 (O(1)) 额外空间,更多体现的是「利用递归回溯模拟双向扫描」的思路。
LeetCode 141. 环形链表 ↑
题目
给你一个链表的头节点 head,判断链表中是否存在环。
如果链表中存在某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 仅用于标识环的实际情况,不作为参数传递。
如果链表中存在环,则返回 true;否则,返回 false。
提示
- 链表中节点数目在范围 ([0, 10^4]) 以内
- (-10^5 ≤ Node.val ≤ 10^5)
pos为-1或者链表中的一个有效索引
进阶:你能否使用 (O(1))(即常量)内存解决此题?
代码(Python)解法一:用列表记录访问过的节点
class Solution(object):
def hasCycle(self, head):
"""
:type head: ListNode
:rtype: bool
"""
record = []
p = head
while p:
if p in record:
return True
record.append(p)
p = p.next
return False
思路解析(解法一)
- 核心想法:顺着链表往后走的过程中,用容器
record记录已经访问过的节点指针。每访问到一个新节点p,先判断它是否已经在record中:如果是,说明我们又回到了之前访问过的节点,链表存在环;若不是,再把它加入record继续向后走,直到指针为None。 - 等价表述:本质上是在遍历时「检查是否访问过同一个节点两次」,这和图论里通过标记结点判断是否成环的思路类似,只不过这里结构是单向链表。
- 边界情况:当
head为None或链表最终走到p is None时,说明尾结点指向的是None,不可能存在环,函数返回False。 - 复杂度与不足:当前实现使用列表存指针、用
p in record做查找,最坏情况下时间复杂度接近 (O(n^2));可以将record改为set()把查找平均降到 (O(1))。总体空间复杂度为 (O(n)),需要额外存储访问过的节点,未达到进阶要求的 (O(1)) 额外空间;进阶通常使用快慢指针(Floyd 判圈)实现常数空间。
代码(Python)解法二:快慢指针(Floyd 判圈算法)
class Solution(object):
def hasCycle(self, head):
"""
:type head: ListNode
:rtype: bool
"""
if not head or not head.next:
return False
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:
return True
return False
思路解析(解法二)
- 快慢指针直观理解:同时从头结点出发设置两个指针——
slow每次走一步,fast每次走两步。如果链表中存在环,那么「快」指针总会在环内追上「慢」指针(类似操场上两人绕圈跑),一旦fast == slow,即可判定有环。 - 无环情况:如果链表没有环,
fast或fast.next总会先走到None,循环条件fast is not None and fast.next is not None失败,从而跳出循环并返回False。 - 边界处理:开头用
if not head or not head.next过滤掉长度为 0 或 1 的链表,这两种情况不可能成环(因为题意中尾指针要么指向某个节点,要么为None)。 - 复杂度与优点:时间复杂度为 (O(n)),空间复杂度为 (O(1)),只使用了常数个指针变量,是满足进阶要求的经典解法;相比解法一不需要额外的数组或集合存储访问记录。

浙公网安备 33010602011771号