1. 双指针与链表算法精讲
今天要跟大家分享的是算法刷题中两个高频考点:双指针技巧和链表操作。作为力扣和卡码网上的常客,这两个知识点几乎出现在每场技术面试中。我整理了9道经典题目,帮你一次性掌握核心解题思路。
双指针不是什么新概念,但它的变体和应用场景之多,让很多初学者头疼。而链表作为基础数据结构,看似简单,实际coding时指针操作的各种坑,没踩过的人根本想象不到。接下来我会结合具体题目,拆解其中的关键技巧和避坑指南。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 双指针算法深度解析
2.1 同向双指针实战
力扣第27题(移除元素)是典型的同向双指针应用。快指针遍历数组,慢指针标记有效位置。这种模式时间复杂度O(n),空间复杂度O(1),是原地操作的典范。
python复制def removeElement(nums, val):
slow = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
return slow
关键点:慢指针只有在元素符合条件时才移动,并接收快指针的值。这种写法避免了元素重复赋值。
2.2 相向双指针应用
力扣第167题(两数之和II)展示了相向指针的威力。在有序数组中,一个指针从头开始,一个从尾出发,根据和的大小调整指针位置:
python复制def twoSum(numbers, target):
left, right = 0, len(numbers)-1
while left < right:
current_sum = numbers[left] + numbers[right]
if current_sum == target:
return [left+1, right+1]
elif current_sum < target:
left += 1
else:
right -= 1
return [-1, -1]
经验:有序数组是相向双指针的最佳拍档,比哈希表解法更省空间。
3. 链表操作核心技巧
3.1 虚拟头节点技巧
力扣第203题(移除链表元素)演示了虚拟头节点的必要性。没有dummy节点时,头节点删除需要特殊处理:
python复制def removeElements(head, val):
dummy = ListNode(next=head)
curr = dummy
while curr.next:
if curr.next.val == val:
curr.next = curr.next.next
else:
curr = curr.next
return dummy.next
避坑指南:链表操作中80%的边界问题都能用dummy节点解决。记住:当需要修改头节点时,就用dummy。
3.2 快慢指针找中点
力扣第876题(链表的中间节点)的快慢指针解法,是面试必考:
python复制def middleNode(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
技术细节:fast每次走两步,slow每次走一步。当fast到达末尾时,slow正好在中点。注意循环条件是fast和fast.next都存在。
4. 进阶题目剖析
4.1 双指针解决滑动窗口
力扣第209题(长度最小的子数组)需要滑动窗口技巧:
python复制def minSubArrayLen(target, nums):
left = total = 0
min_len = float('inf')
for right in range(len(nums)):
total += nums[right]
while total >= target:
min_len = min(min_len, right-left+1)
total -= nums[left]
left += 1
return min_len if min_len != float('inf') else 0
调试心得:内层while循环容易写成if,但实际需要持续收缩左边界直到不满足条件。
4.2 链表反转的四种写法
力扣第206题(反转链表)有迭代和递归两种主流解法:
迭代法:
python复制def reverseList(head):
prev = None
curr = head
while curr:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
return prev
递归法:
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
对比分析:迭代法空间O(1),递归法空间O(n)但有更简洁的代码。面试时建议先写迭代法。
5. 常见错误排查指南
5.1 指针丢失问题
在链表操作中,最常见的错误是在修改指针前没有保存后续节点:
错误示范:
python复制curr.next = prev # 丢失了原始curr.next的引用
prev = curr
curr = curr.next # 这里curr.next已经是prev了
正确做法:
python复制next_node = curr.next # 先保存
curr.next = prev
prev = curr
curr = next_node
5.2 边界条件检查
双指针问题必须考虑这些边界情况:
- 空数组输入
- 所有元素都需要移除的情况
- 目标值在首尾位置
- 链表只有一个节点或为空
6. 刷题路线建议
根据题目难度梯度训练:
- 双指针入门:27(移除元素)、26(去重)、283(移动零)
- 链表基础:203(移除元素)、206(反转)、876(中点)
- 进阶挑战:15(三数之和)、142(环形链表II)、19(删除倒数第N个节点)
个人经验:每天保持3道题的手感,重点不是数量而是吃透每种模式。建议用力扣的探索卡片系统学习。
7. 性能优化技巧
7.1 减少不必要的计算
在双指针问题中,可以通过提前判断来优化:
python复制# 优化前
while left < right:
current_sum = nums[left] + nums[right]
if current_sum == target:
return [left+1, right+1]
elif current_sum < target:
left += 1
else:
right -= 1
# 优化后:跳过重复元素
while left < right:
current_sum = nums[left] + nums[right]
if current_sum == target:
return [left+1, right+1]
elif current_sum < target:
while left < right and nums[left] == nums[left+1]:
left += 1
left += 1
else:
while left < right and nums[right] == nums[right-1]:
right -= 1
right -= 1
7.2 空间复杂度优化
链表问题通常要求O(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
这种写法比哈希表解法节省了O(n)的空间。
8. 面试实战策略
8.1 解题步骤建议
- 先厘清问题:向面试官确认输入输出、边界条件
- 举例说明:用具体例子演示解题思路
- 先给出暴力解法:再逐步优化
- 讨论复杂度:时间/空间复杂度分析
- 编写代码:注意变量命名和代码风格
- 测试用例:至少覆盖3种边界情况
8.2 高频考点总结
根据近期面试反馈,这些知识点出现频率最高:
- 双指针:三数之和、接雨水问题
- 链表:反转链表、环形检测、合并有序链表
- 综合题:链表排序、重排链表
9. 扩展学习资源
- 图解算法数据结构:https://leetcode.cn/leetbook/
- labuladong的算法笔记:系统性的解题框架
- 剑指Offer:经典面试题精讲
- 卡码网编程能力提升计划:针对性训练
最后分享一个调试技巧:在链表问题中,可以自定义打印函数来可视化指针位置:
python复制def print_list(head):
curr = head
while curr:
print(curr.val, end=" -> ")
curr = curr.next
print("None")
这个简单的工具能帮你快速定位指针操作的问题。记住,算法能力的提升没有捷径,但正确的方法能让你的刷题效率翻倍。保持每天至少一道题的节奏,三个月后你会看到明显的进步。
