链表作为数据结构中的经典类型,在技术面试中出现的频率仅次于数组。不同于数组的连续存储特性,链表的节点分散存储并通过指针相连,这种结构特性决定了它在插入删除操作上的优势,同时也带来了遍历和查找的挑战。在近三年国内一线互联网企业的算法面试统计中,链表相关题目占比高达32%,其中回文链表和相交链表这两类问题更是高频中的高频。
为什么面试官如此青睐这两类问题?从考核维度来看:
最直观的解法是将链表值复制到数组后用双指针判断,但这样需要O(n)额外空间。面试中更期待看到空间复杂度O(1)的解法:
python复制def isPalindrome(head):
# 快慢指针找中点
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# 反转后半部分
prev = None
while slow:
temp = slow.next
slow.next = prev
prev = slow
slow = temp
# 前后半部比较
left, right = head, prev
while right:
if left.val != right.val:
return False
left = left.next
right = right.next
return True
关键点:快慢指针的终止条件决定了中点处理方式,当链表长度为奇数时,slow会停在正中间,这个节点不需要参与比较
递归解法虽然空间复杂度仍是O(n),但能展示对链表递归的理解深度:
python复制def isPalindrome(head):
self.front = head
def recursiveCheck(node):
if node:
if not recursiveCheck(node.next):
return False
if self.front.val != node.val:
return False
self.front = self.front.next
return True
return recursiveCheck(head)
递归深度与链表长度成正比,实际面试中建议先说明栈空间消耗,再给出迭代解法作为优化。
初级解法通常使用哈希表存储节点,空间复杂度O(m+n)。最优解利用数学规律实现O(1)空间:
python复制def getIntersectionNode(headA, headB):
pA, pB = headA, headB
while pA != pB:
pA = pA.next if pA else headB
pB = pB.next if pB else headA
return pA
这个算法的精妙之处在于:
实际编码时需要特别注意:
适用于:链表中点、环检测、回文判断等问题
python复制def template_slow_fast(head):
slow = fast = head
while fast and fast.next: # 根据问题调整终止条件
slow = slow.next # 通常步长为1
fast = fast.next.next # 通常步长为2
# 后续处理逻辑
# ...
包含迭代和递归两种实现:
python复制# 迭代法
def reverse_list(head):
prev = None
while head:
temp = head.next
head.next = prev
prev = head
head = temp
return prev
# 递归法
def reverse_list_recursive(head):
if not head or not head.next:
return head
p = reverse_list_recursive(head.next)
head.next.next = head
head.next = None
return p
在反转链表或节点交换时,容易丢失后续节点引用。正确做法是先保存next节点:
python复制# 错误示范
node.next = prev # 此时丢失了原node.next的引用
prev = node
node = node.next # 实际指向了prev
# 正确做法
temp = node.next
node.next = prev
prev = node
node = temp
快慢指针的不同变体需要仔细设计终止条件:
| 问题类型 | 快指针条件 | 慢指针位置 |
|---|---|---|
| 找中点 | fast and fast.next | 长度奇数时正中间 |
| 检测环 | fast and fast.next | 与快指针相遇点 |
| 找倒数第k个 | fast先走k步 | 与fast同步 |
处理头节点可能变化的场景时,使用dummy节点能简化逻辑:
python复制dummy = ListNode(0)
dummy.next = head
# ...处理逻辑...
return dummy.next
当面试官追问进一步优化时,可以考虑:
在时间复杂度相同情况下,可以展示的优化点:
python复制# 优化后的回文判断
while right and left.val == right.val: # 合并判断条件
left = left.next
right = right.next
链表问题的优化往往体现在指针操作的优雅性上,这需要大量的刻意练习。建议每天至少手写3种不同变体的链表操作,持续2周后会有显著提升。在面试白板编码时,建议先用注释写出关键步骤,再填充代码,这样可以避免指针混乱的情况。