1. 链表算法精要:LeetCode Top100 Python实现指南
链表作为数据结构中的基础类型,在算法面试中占据着重要地位。本文将以Python实现LeetCode链表类Top100题目,通过8个经典案例深入剖析链表操作的核心技巧。不同于简单的答案罗列,我将结合多年刷题和面试官经验,揭示每种解法背后的设计思路和优化逻辑。
2. 基础操作与双指针技巧
2.1 相交链表(#160)
问题本质:判断两个链表是否相交,并找出第一个公共节点。关键在于处理不同长度链表带来的遍历不同步问题。
哈希法缺陷:
python复制def getIntersectionNode(headA, headB):
nodes = set()
while headA:
nodes.add(headA)
headA = headA.next
while headB:
if headB in nodes:
return headB
headB = headB.next
return None
虽然直观,但空间复杂度O(n)不符合进阶要求。当链表长度超过10^6时,集合存储会消耗大量内存。
双指针精妙解法:
python复制class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
p1, p2 = headA, headB
while p1 != p2:
p1 = p1.next if p1 else headB
p2 = p2.next if p2 else headA
return p1
关键洞察:
- 指针交替遍历消除了长度差:假设链表A独立部分为a,链表B独立部分为b,公共部分为c。当p1走完a+c+b时,p2正好走完b+c+a,两者会在交点相遇
- 时间复杂度O(m+n),空间复杂度O(1)
- 即使无交点,两指针最终都会指向None退出循环
2.2 反转链表(#206)
迭代法标准实现:
python复制def reverseList(head):
prev = None
curr = head
while curr:
next_temp = curr.next
curr.next = prev
prev = curr
curr = next_temp
return prev
递归解法精髓:
python复制def reverseList(head):
if not head or not head.next:
return head
p = reverseList(head.next)
head.next.next = head # 关键步骤:让下一个节点指向自己
head.next = None # 断开原指向防止循环
return p
工程实践建议:
- 迭代法更适合生产环境,递归在长链表时可能栈溢出
- 注意断开原链表的next指针,否则会产生循环引用
- 对于Python而言,递归写法更简洁但性能稍差
3. 快慢指针的妙用
3.1 环形链表检测(#141)
Floyd判圈算法实现:
python复制def hasCycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
算法分析:
- 时间复杂度O(n):快指针每次两步,慢指针每次一步
- 空间复杂度O(1):仅使用两个指针
- 数学证明:设环前长度L,环长度C,相遇时慢指针走了L+D,快指针走了L+D+kC,由2(L+D)=L+D+kC得L=(k-1)C+(C-D)
3.2 环形链表入口定位(#142)
进阶解法:
python复制def detectCycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
return None
关键理解:
- 第一阶段同#141,先找到相遇点
- 第二阶段将慢指针重置到head,两指针同速前进,再次相遇点即为环入口
- 该结论可由前文数学关系L=(k-1)C+(C-D)推导得出
4. 链表合并与分解
4.1 合并有序链表(#21)
迭代法标准实现:
python复制def mergeTwoLists(l1, l2):
dummy = curr = ListNode(0)
while l1 and l2:
if l1.val < l2.val:
curr.next = l1
l1 = l1.next
else:
curr.next = l2
l2 = l2.next
curr = curr.next
curr.next = l1 or l2
return dummy.next
递归优雅解法:
python复制def mergeTwoLists(l1, l2):
if not l1: return l2
if not l2: return l1
if l1.val < l2.val:
l1.next = mergeTwoLists(l1.next, l2)
return l1
else:
l2.next = mergeTwoLists(l1, l2.next)
return l2
性能对比:
| 方法 | 时间复杂度 | 空间复杂度 | 代码简洁度 | 栈溢出风险 |
|---|---|---|---|---|
| 迭代法 | O(m+n) | O(1) | 中等 | 无 |
| 递归法 | O(m+n) | O(m+n) | 高 | 长链表时存在 |
5. 复杂链表操作
5.1 两数相加(#2)
模拟竖式计算:
python复制def addTwoNumbers(l1, l2):
dummy = curr = ListNode(0)
carry = 0
while l1 or l2 or carry:
v1 = l1.val if l1 else 0
v2 = l2.val if l2 else 0
sum_val = v1 + v2 + carry
carry = sum_val // 10
curr.next = ListNode(sum_val % 10)
curr = curr.next
l1 = l1.next if l1 else None
l2 = l2.next if l2 else None
return dummy.next
边界处理要点:
- 处理链表长度不一致的情况
- 最后进位不为零时需要新增节点
- 时间复杂度O(max(m,n)),空间复杂度O(max(m,n))
5.2 删除倒数第N个节点(#19)
单次遍历技巧:
python复制def removeNthFromEnd(head, n):
dummy = ListNode(0, head)
slow = fast = dummy
for _ in range(n):
fast = fast.next
while fast.next:
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return dummy.next
关键细节:
- 使用dummy节点统一处理头节点删除的情况
- 快指针先走n步,然后同步移动直到快指针到达末尾
- 慢指针此时指向待删除节点的前驱
6. 高级翻转技巧
6.1 两两交换节点(#24)
指针操作艺术:
python复制def swapPairs(head):
dummy = prev = ListNode(0, head)
while head and head.next:
first = head
second = head.next
prev.next = second
first.next = second.next
second.next = first
prev = first
head = first.next
return dummy.next
递归解法:
python复制def swapPairs(head):
if not head or not head.next:
return head
new_head = head.next
head.next = swapPairs(new_head.next)
new_head.next = head
return new_head
操作示意图:
code复制初始状态: dummy -> 1 -> 2 -> 3 -> 4
第一步: dummy -> 2, 1 -> 3, 2 -> 1
第二步: 1 -> 4, 3 -> None, 4 -> 3
结果: dummy -> 2 -> 1 -> 4 -> 3
6.2 K个一组翻转(#25)
分段翻转策略:
python复制def reverseKGroup(head, k):
def reverse(head, k):
prev = None
curr = head
while k:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
k -= 1
return prev
dummy = ListNode(0, head)
group_prev = dummy
while True:
kth = group_prev
for _ in range(k):
kth = kth.next
if not kth:
return dummy.next
group_next = kth.next
# 反转当前组
prev, curr = reverse(group_prev.next, k)
# 重新连接
group_prev.next = prev
curr.next = group_next
group_prev = curr
复杂度分析:
- 时间复杂度O(n):每个节点被处理两次(遍历和反转)
- 空间复杂度O(1):仅使用固定数量的指针
7. 特殊链表处理
7.1 随机指针链表复制(#138)
三步复制法:
python复制def copyRandomList(head):
if not head: return None
# 第一步:插入复制节点
curr = head
while curr:
new_node = Node(curr.val, curr.next)
curr.next = new_node
curr = new_node.next
# 第二步:设置random指针
curr = head
while curr:
if curr.random:
curr.next.random = curr.random.next
curr = curr.next.next
# 第三步:分离链表
old = head
new = head.next
new_head = head.next
while old:
old.next = old.next.next
new.next = new.next.next if new.next else None
old = old.next
new = new.next
return new_head
算法优势:
- 不需要额外空间存储节点映射关系
- 时间复杂度O(n)的三次线性遍历
- 保持原链表结构不变
7.2 链表排序(#148)
归并排序实现:
python复制def sortList(head):
if not head or not head.next:
return head
# 分割链表
slow, fast = head, head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
mid = slow.next
slow.next = None
# 递归排序
left = sortList(head)
right = sortList(mid)
# 合并
dummy = curr = ListNode(0)
while left and right:
if left.val < right.val:
curr.next = left
left = left.next
else:
curr.next = right
right = right.next
curr = curr.next
curr.next = left or right
return dummy.next
性能特点:
- 时间复杂度O(nlogn):标准归并排序复杂度
- 空间复杂度O(logn):递归栈深度
- 相比数组排序,不需要随机访问特性,适合链表结构
8. 链表算法实战要点
调试技巧:
- 绘制链表图示:在纸上画出指针变化过程
- 使用小规模测试用例:如空链表、单节点链表等边界情况
- 添加打印语句:在关键步骤输出指针指向的值
常见错误:
- 指针丢失:在修改next指针前没有保存后续节点
- 循环引用:反转链表时未正确处理尾节点
- 边界条件:未处理头节点/尾节点的特殊情况
优化方向:
- 多考虑双指针技巧
- 善用哑节点简化边界处理
- 递归解法虽然简洁但要注意栈深度限制
链表问题的核心在于指针操作,掌握这些经典问题的解法后,90%的链表相关问题都能迎刃而解。建议读者亲自动手实现每个算法,通过实际编码加深对指针操作的理解。