1. 链表算法实战:从LeetCode高频题到工程思维
链表作为数据结构中的常青树,在算法面试和实际开发中都有着不可替代的地位。我整理了十道LeetCode上最经典的链表题目,这些题目覆盖了链表操作的所有核心模式。不同于教科书上的理论讲解,这里我会结合真实的代码实现和工程中的优化思路,带你掌握链表问题的解决之道。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础操作三连:反转、相交与回文
2.1 206反转链表 - 指针操作的基石
反转链表是理解指针操作的最佳入门题。常规的迭代解法需要维护prev、curr和next三个指针:
python复制def reverseList(head):
prev = None
curr = head
while curr:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
return prev
但实际面试中,面试官可能会要求用递归实现。递归版本虽然空间复杂度是O(n),但能很好地考察对调用栈的理解:
python复制def reverseList(head):
if not head or not head.next:
return head
new_head = reverseList(head.next)
head.next.next = head
head.next = None
return new_head
提示:递归解法在链表很长时会导致栈溢出,工程中更推荐迭代解法
2.2 160相交链表 - 空间复杂度的较量
这道题有两个经典解法:哈希表法(O(n)空间)和双指针法(O(1)空间)。哈希表法直观但不够优雅:
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
而双指针法则展现了链表操作的智慧:
python复制def getIntersectionNode(headA, headB):
p1, p2 = headA, headB
while p1 != p2:
p1 = p1.next if p1 else headB
p2 = p2.next if p2 else headA
return p1
2.3 234回文链表 - 快慢指针的经典应用
判断回文链表的标准解法是:
- 用快慢指针找到中点
- 反转后半部分
- 比较前后两部分
python复制def isPalindrome(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
prev = None
while slow:
next_node = slow.next
slow.next = prev
prev = slow
slow = next_node
while prev:
if head.val != prev.val:
return False
head = head.next
prev = prev.next
return True
注意:这种方法会修改原链表,如果要求不修改原链表,可以用O(n)空间存储节点值再判断
3. 环形链表:检测与入口定位
3.1 141环形链表 - 弗洛伊德判圈法
判断链表是否有环的快慢指针解法:
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
3.2 142环形链表II - 数学推导找入口
找到环的入口需要一些数学推导。当快慢指针相遇时,让其中一个指针回到head,然后同速前进,再次相遇点就是环入口:
python复制def detectCycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
break
else:
return None
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
这个解法背后的数学原理是:设环外长度为a,环内相遇点距环入口为b,剩余环长为c,则有2(a+b)=a+b+k(b+c),推导出a=(k-1)(b+c)+c
4. 链表综合应用:从两数相加到随机复制
4.1 2两数相加 - 链表表示的大数运算
这道题模拟了用链表存储的大数相加过程,需要注意进位处理:
python复制def addTwoNumbers(l1, l2):
dummy = ListNode(0)
curr = dummy
carry = 0
while l1 or l2 or carry:
sum_val = carry
if l1:
sum_val += l1.val
l1 = l1.next
if l2:
sum_val += l2.val
l2 = l2.next
carry, val = divmod(sum_val, 10)
curr.next = ListNode(val)
curr = curr.next
return dummy.next
4.2 138随机链表复制 - 哈希与链表的结合
带随机指针的链表复制问题,最优解法是O(n)时间O(1)空间的交错复制法:
python复制def copyRandomList(head):
if not head:
return None
# 第一步:在每个原节点后面创建一个新节点
curr = head
while curr:
new_node = Node(curr.val)
new_node.next = 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 = head
new_head = head.next
curr_old = old_head
curr_new = new_head
while curr_old:
curr_old.next = curr_old.next.next
curr_new.next = curr_new.next.next if curr_new.next else None
curr_old = curr_old.next
curr_new = curr_new.next
return new_head
5. 链表排序与高级操作
5.1 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 = ListNode(0)
curr = dummy
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 if left else right
return dummy.next
5.2 24两两交换节点 - 指针操作的熟练度测试
这道题考察指针操作的熟练程度,需要处理好各种边界条件:
python复制def swapPairs(head):
dummy = ListNode(0)
dummy.next = head
prev = dummy
while prev.next and prev.next.next:
first = prev.next
second = first.next
# 交换节点
first.next = second.next
second.next = first
prev.next = second
# 移动prev指针
prev = first
return dummy.next
6. 工程实践中的链表优化
在实际工程中,链表的使用远比算法题复杂。比如Linux内核中的链表实现就采用了侵入式设计,将链表节点嵌入到数据结构中:
c复制struct list_head {
struct list_head *next, *prev;
};
struct task_struct {
// 其他字段...
struct list_head tasks;
// 其他字段...
};
这种设计使得任何数据结构都可以被链接,而不需要为每种数据类型定义新的链表结构。
另一个工程实践是使用哨兵节点(dummy node)简化边界条件处理,就像我们在很多LeetCode解法中做的那样。这能显著减少代码中的条件判断,提高可读性和健壮性。
