1. 算法刷题实战:双指针与链表的9道经典题解
作为一名刷过3000+算法题的过来人,我深刻理解双指针和链表在面试中的高频出现率。今天要分享的9道题目覆盖了这两个核心数据结构的典型应用场景,其中每道题都经过我反复验证,附上独家优化技巧。这些题目全部来自力扣和卡码网的热门题库,建议先收藏再实践。
双指针算法看似简单,但实际应用中80%的失误都源于边界条件处理不当。而链表问题更是算法面试的"常青树",大厂技术面中几乎必考。本文的解题方案都经过工程实践验证,包含时间复杂度优化细节和易错点分析,特别适合准备秋招或日常刷题的同学。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 双指针算法核心原理与实战
2.1 双指针的三种经典模式
双指针算法本质是通过维护两个指针变量来降低问题复杂度,主要分为以下三种模式:
- 对撞指针:常用于有序数组,指针从两端向中间移动(如两数之和)
- 快慢指针:解决环形检测、中点查找等问题(如环形链表)
- 滑动窗口:处理子串/子数组问题(如最小覆盖子串)
以力扣第167题"两数之和II-输入有序数组"为例,标准解法就是对撞指针的典型应用:
python复制def twoSum(numbers, target):
left, right = 0, len(numbers)-1
while left < right:
sum = numbers[left] + numbers[right]
if sum == target:
return [left+1, right+1]
elif sum < target:
left += 1
else:
right -= 1
return [-1, -1]
关键细节:循环条件必须是
left < right而非<=,否则在偶数个元素时会产生重复使用同一元素的情况。
2.2 高频题目精讲
2.2.1 删除排序数组中的重复项(力扣26题)
这道题要求原地删除有序数组中的重复元素,返回新长度。快慢指针解法的时间复杂度是O(n),空间复杂度O(1):
python复制def removeDuplicates(nums):
if not nums:
return 0
slow = 0
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1
易错点分析:
- 忘记处理空数组的特殊情况
- 慢指针初始位置应为0而非1
- 比较条件应该是
nums[fast] != nums[slow]而非nums[fast] != nums[fast-1]
2.2.2 盛最多水的容器(力扣11题)
这道题需要找到两条线段,使得它们与x轴构成的容器能容纳最多的水。对撞指针解法非常高效:
python复制def maxArea(height):
left, right = 0, len(height)-1
max_area = 0
while left < right:
area = min(height[left], height[right]) * (right - left)
max_area = max(max_area, area)
if height[left] < height[right]:
left += 1
else:
right -= 1
return max_area
优化技巧:
- 每次移动较矮的那端指针,因为容器的盛水量由较矮的一端决定
- 可以提前计算理论最大可能值进行剪枝优化
3. 链表专题深度解析
3.1 链表操作的四个核心技巧
- 虚拟头节点:处理头节点可能被删除的情况(dummy node)
- 快慢指针:找中点、判环、找环入口
- 多指针协同:反转链表、分组反转等
- 递归思想:从后向前处理链表问题
3.2 必刷题目详解
3.2.1 反转链表(力扣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
# 递归法
def reverseListRecursive(head):
if not head or not head.next:
return head
p = reverseListRecursive(head.next)
head.next.next = head
head.next = None
return p
常见误区:
- 迭代法中忘记保存next节点导致链表断裂
- 递归法中没有正确处理尾节点的next指针
3.2.2 环形链表II(力扣142题)
这道题需要找出环形链表的入口节点,是快慢指针的经典应用:
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
ptr = head
while ptr != slow:
ptr = ptr.next
slow = slow.next
return ptr
数学原理:
设头节点到入口距离为a,入口到相遇点距离为b,环长为c
根据快慢指针速度关系可得:2(a+b) = a+b+kc ⇒ a = (k-1)c + (c-b)
这意味着从相遇点和头节点同时出发,必在入口处相遇
4. 双指针与链表的组合应用
4.1 相交链表(力扣160题)
这道题需要找到两个链表的相交节点,空间复杂度要求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
算法思路:
- 让两个指针分别遍历两个链表
- 到达末尾时切换到另一链表的头部
- 如果相交会在交点相遇,否则同时到达None
4.2 删除链表的倒数第N个节点(力扣19题)
快慢指针的典型应用,需要注意删除头节点的情况:
python复制def removeNthFromEnd(head, n):
dummy = ListNode(0)
dummy.next = head
fast = slow = dummy
for _ in range(n):
fast = fast.next
while fast.next:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy.next
注意事项:
- 使用dummy节点统一处理头节点删除的情况
- 快指针先走n步后,慢指针再开始移动
- 循环条件是fast.next而非fast,确保slow停在目标前驱
5. 高级应用与优化技巧
5.1 合并K个升序链表(力扣23题)
这道题可以用最小堆优化到O(Nlogk)时间复杂度:
python复制import heapq
def mergeKLists(lists):
min_heap = []
for i, lst in enumerate(lists):
if lst:
heapq.heappush(min_heap, (lst.val, i, lst))
dummy = ListNode(0)
curr = dummy
while min_heap:
val, i, node = heapq.heappop(min_heap)
curr.next = node
curr = curr.next
if node.next:
heapq.heappush(min_heap, (node.next.val, i, node.next))
return dummy.next
性能优化点:
- 堆中存储三元组(val, index, node)避免直接比较节点对象
- 每次只push当前节点的下一个节点,保持堆大小不超过k
- 时间复杂度从O(Nk)优化到O(Nlogk)
5.2 LRU缓存机制(力扣146题)
结合哈希表和双向链表实现O(1)时间复杂度的LRU缓存:
python复制class DLinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity):
self.cache = {}
self.capacity = capacity
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
self.size = 0
def get(self, key):
if key not in self.cache:
return -1
node = self.cache[key]
self.moveToHead(node)
return node.value
def put(self, key, value):
if key in self.cache:
node = self.cache[key]
node.value = value
self.moveToHead(node)
else:
node = DLinkedNode(key, value)
self.cache[key] = node
self.addToHead(node)
self.size += 1
if self.size > self.capacity:
removed = self.removeTail()
del self.cache[removed.key]
self.size -= 1
def addToHead(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def removeNode(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def moveToHead(self, node):
self.removeNode(node)
self.addToHead(node)
def removeTail(self):
node = self.tail.prev
self.removeNode(node)
return node
设计要点:
- 双向链表维护访问顺序,最近访问的放在头部
- 哈希表提供O(1)的节点访问
- 达到容量时移除尾部节点并同步删除哈希表项
6. 常见问题与调试技巧
6.1 链表问题的调试方法
-
可视化工具:
- 使用力扣的链表可视化功能
- 打印链表时加上箭头连接:
1->2->3->None
-
边界测试用例:
- 空链表
- 单节点链表
- 头节点/尾节点操作
-
循环检测:
python复制def printList(head): visited = set() while head: if head in visited: print("Cycle detected!") break visited.add(head) print(head.val, end=" -> ") head = head.next print("None")
6.2 双指针算法的常见错误
-
指针移动条件错误:
- 在滑动窗口问题中错误地移动左指针
- 忘记在移动指针后更新窗口状态
-
边界条件处理不当:
- 数组长度为0或1的情况
- 指针越界访问(如nums[right+1])
-
循环终止条件错误:
python复制# 错误示例:可能导致无限循环 while left <= right: # ... -
多语言实现的差异:
- Python中
for循环的range与C++的for循环区别 - Java的整数溢出问题
- Python中
7. 刷题策略与进阶路线
7.1 高效刷题四步法
- 理解题意:画出示例的输入输出关系
- 暴力解法:先写出时间复杂度较高的解法
- 分析优化:识别重复计算或冗余操作
- 模板转化:套用已知算法模式(如双指针)
7.2 进阶学习资源
-
经典教材:
- 《算法导论》中的链表相关章节
- 《编程珠玑》中的双指针技巧
-
在线课程:
- 力扣的探索卡片"链表"和"双指针"专题
- 卡码网的算法基础课程
-
实战平台:
- 力扣热题100中的链表和双指针题目
- Codeforces的Div2竞赛题
在实际刷题过程中,我发现将每道题的解题思路用思维导图记录下来特别有效。对于双指针问题,明确指针移动条件和终止条件是关键;而对于链表问题,画图辅助分析指针变化能避免90%的错误。建议每天保持3-5题的练习量,重点题目要反复做直到能bug-free写出。
