一、为什么需要算法刷题错题本
刚开始刷算法题的时候,很多人都会遇到这样的情况:明明之前做过类似的题目,但再遇到时还是不会做,或者写出来的代码漏洞百出。这其实就是因为没有系统性地整理和分析错题,导致同样的错误反复出现。
错题本的作用,就是帮助我们:
- 归类错题:把相似的错误整理到一起,方便后续复习。
- 分析原因:找出自己到底是思路问题、代码实现问题,还是对算法理解不透彻。
- 避免重复犯错:通过总结规律,减少未来犯同样错误的概率。
举个例子,你在做LeetCode 上的二叉树遍历问题时,可能会因为递归终止条件没写好导致栈溢出,或者迭代写法时忘记处理空节点。如果不记录下来,下次遇到类似问题还是会栽跟头。
二、如何有效归类错题
归类错题的核心是按算法类型和错误类型进行分类。我们可以用表格或者笔记软件(如 Notion、OneNote)来管理。
1. 按算法类型分类
- 数据结构类:数组、链表、栈、队列、哈希表、堆、树、图等。
- 算法类:排序、二分查找、DFS/BFS、动态规划、贪心、回溯、双指针等。
比如:
| 类别 | 题目 | 错误原因 |
|------------|-----------------------|----------------------------|
| 动态规划 | LeetCode 70.爬楼梯 | 递推关系没想清楚,初始条件错误 |
| 双指针 | LeetCode 11.盛最多水的容器 | 左右指针移动条件判断错误 |
2. 按错误类型分类
- 逻辑错误:比如循环条件写错、边界条件没考虑。
- 语法错误:比如 Python 的缩进问题、Java 的数组越界。
- 优化不足:比如暴力解法超时,没想到更优解。
- 理解偏差:比如题目意思理解错了,导致代码完全跑偏。
示例(Python 技术栈):
# 错误示例:LeetCode 15.三数之和(双指针解法)
def threeSum(nums):
res = []
nums.sort()
for i in range(len(nums)):
if i > 0 and nums[i] == nums[i-1]: # 去重逻辑错误,应该放在找到解之后
continue
left, right = i+1, len(nums)-1
while left < right:
s = nums[i] + nums[left] + nums[right]
if s == 0:
res.append([nums[i], nums[left], nums[right]])
# 忘记移动指针,导致死循环
elif s < 0:
left += 1
else:
right -= 1
return res
修正后:
def threeSum(nums):
res = []
nums.sort()
for i in range(len(nums)):
if i > 0 and nums[i] == nums[i-1]: # 跳过重复元素
continue
left, right = i+1, len(nums)-1
while left < right:
s = nums[i] + nums[left] + nums[right]
if s == 0:
res.append([nums[i], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]: # 去重
left += 1
while left < right and nums[right] == nums[right-1]: # 去重
right -= 1
left += 1 # 必须移动指针
right -= 1
elif s < 0:
left += 1
else:
right -= 1
return res
三、如何深入分析错误原因
归类之后,更重要的是分析为什么出错,而不仅仅是记录题目。
1. 错误原因拆解
- 思路问题:是不是一开始就没想清楚?比如动态规划的状态转移方程没列对。
- 代码实现问题:比如递归终止条件漏了,或者循环变量更新错误。
- 边界条件:比如数组为空、链表只有一个节点等特殊情况没考虑。
示例(Java 技术栈):
// 错误示例:LeetCode 206.反转链表(递归解法)
public ListNode reverseList(ListNode head) {
if (head == null) return null; // 终止条件没问题
ListNode newHead = reverseList(head.next);
head.next.next = head; // 问题:没有处理 head.next 为 null 的情况
head.next = null; // 导致空指针异常
return newHead;
}
修正后:
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) { // 增加 head.next == null 的判断
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
2. 对比优秀解法
有时候,你的代码可能能跑通,但不够优雅或高效。这时候可以去看看别人的解法,学习更优的思路。
比如,同样是反转链表,迭代写法可能更直观:
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next; // 保存下一个节点
curr.next = prev; // 反转指针
prev = curr; // 移动 prev
curr = next; // 移动 curr
}
return prev;
}
四、如何避免重复犯错
1. 定期复习错题
- 每周抽时间回顾错题本,重新写一遍曾经出错的代码。
- 重点关注那些反复出错的题目,直到完全掌握。
2. 模拟面试场景
- 随机抽一道错题,限时 15-20 分钟完成,模拟真实面试环境。
- 如果还是写不出来,就继续归类到“未掌握”列表,加强练习。
3. 总结通用解题模板
很多算法题有固定套路,整理出模板可以大幅减少错误。
比如二分查找的通用模板(Python):
def binary_search(nums, target):
left, right = 0, len(nums) - 1
while left <= right: # 注意是 <=
mid = left + (right - left) // 2 # 防止溢出
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1 # 注意 +1
else:
right = mid - 1 # 注意 -1
return -1
4. 刻意练习薄弱点
如果你发现自己在动态规划或图论问题上总是出错,就集中刷这类题目,直到形成肌肉记忆。
五、应用场景与技术优缺点
1. 应用场景
- 面试准备:帮助快速查漏补缺,针对性提升。
- 竞赛刷题:减少低级错误,提高解题速度。
- 日常学习:系统化整理知识,避免学了就忘。
2. 技术优缺点
- 优点:
- 提高刷题效率,减少无效练习。
- 帮助形成系统化的解题思维。
- 缺点:
- 需要额外时间整理,初期可能觉得麻烦。
- 如果只是记录不复习,效果会大打折扣。
3. 注意事项
- 不要贪多:每天记录 3-5 道典型错题即可,太多反而难以消化。
- 注重质量:分析一道题的收获,比盲目刷 10 道题更有价值。
- 保持更新:随着水平提升,早期的一些错题可能不再适用,可以适当清理。
六、总结
算法刷题的错题本,本质上是一个不断迭代优化的过程。通过归类、分析、复习,我们可以把“踩过的坑”变成“进步的阶梯”。
关键点回顾:
- 归类错题:按算法类型和错误类型整理。
- 分析原因:拆解思路、代码、边界条件等问题。
- 避免重复犯错:定期复习、模拟面试、总结模板。
坚持下去,你会发现,曾经的难题逐渐变得简单,刷题的速度和准确率也会大幅提升!
评论