BEGINNER
初学者,先建立题型感觉
不用一上来背复杂模板。先知道这道题为什么会想到双指针、DP、栈,再跟着动画走一遍。
- 看每套心法的识别信号
- 点配套简单题,先看动画
- 把题意复述成输入、输出和限制条件
适合谁
同一套心法,不是只给面试党背模板。它要同时解决看不懂、刷不动、讲不清和迁移不了四类问题。
BEGINNER
不用一上来背复杂模板。先知道这道题为什么会想到双指针、DP、栈,再跟着动画走一遍。
CAMPUS
最怕东刷一道、西刷一道。这里把常见题收成专题,帮你从会一道变成会一类。
ADVANCED
只会写代码还不够,高阶题更看重抽象能力:为什么正确,复杂度如何,能不能换场景复用。
学习闭环
01
先判断题里出现的是有序、连续区间、选或不选、最近未处理,还是树的子问题。
02
把题翻译成双指针、DP、回溯、树形递归、二分或栈,再写出最小可用模板。
03
用动画检查状态变化和边界条件,再换一两道同类题验证迁移能力。
六套核心心法
先建立模型,再回到题库练。越是高频的题型,越值得把这套顺序练熟。
省掉一层循环
看到数组、字符串、链表里出现成对比较、连续区间、原地处理,优先想双指针。它的价值是把暴力枚举的两层循环,压成一趟可控移动。
识别信号
有序数组找一对;最长或最短连续子串;链表中点、环;原地删除或移动元素。
常见坑
窗口题忘记维护计数;对撞题没利用有序性;快慢指针忘记处理偶数长度。
# 对撞指针:有序数组里找一对
l, r = 0, n - 1
while l < r:
if 满足(l, r): return ...
elif 偏大: r -= 1
else: l += 1先别背四种写法,记住指针移动必须排除一批不可能答案。每移动一步,都要能说清楚为什么那部分不用再看。
定义状态,再谈转移
DP 不是背公式,而是把一个大问题拆成一组更小的状态。只要状态定义清楚,转移方程通常会从最后一步怎么来里自然出现。
识别信号
求最优值、方案数、可不可行;有重复子问题;当前选择会影响后续。
常见坑
dp[i] 没定义清楚就写公式;初始化缺边界;背包遍历方向写反。
# DP 五问:每次都按这个顺序想
状态是什么?
从哪些更小状态转移?
初始值是什么?
按什么顺序填表?
能否压缩空间?每做完一道 DP,把状态定义和转移来源写成一句话。面试官真正想听的不是代码多熟,而是你为什么这样定义状态。
选,递归,撤销
回溯就是在一棵决策树上搜索。要做的不是乱试,而是定义清楚每层在选什么、哪些选择合法、什么时候收集答案。
识别信号
枚举所有方案;组合、排列、子集;棋盘搜索;需要试错和撤销。
常见坑
组合和排列去重混淆;忘记撤销选择;保存答案时没复制 path。
path.append(选项) # 做选择
backtrack(下一层) # 继续搜
path.pop() # 撤销选择把回溯画成树。每一层代表一个决策位置,每条边代表一次选择。你能画出来,就能写出来。
拿左右子树拼自己
二叉树题的核心不是背遍历顺序,而是想清楚当前节点要向父节点返回什么。递归函数一旦定义准确,前中后序只是处理时机不同。
识别信号
树的深度、路径、对称、最近公共祖先、BST 合法性。
常见坑
返回给父节点的值和全局答案混在一起;空节点基准值设错。
def dfs(node):
if not node: return 基准值
L = dfs(node.left)
R = dfs(node.right)
return 合并(L, R, node)很多树题可以看作结构归纳。先证明空树成立,再假设左右子树答案正确,最后证明合并后当前节点正确。
凡单调,皆可砍半
二分不只是数组里找数。只要答案区间具有单调性,就可以在答案上二分:速度、容量、天数、阈值,都能变成可判定问题。
识别信号
有序数组;找第一个满足条件的位置;最小化最大值;答案越大越容易满足。
常见坑
边界开闭混乱;mid 不推进导致死循环;判定函数的单调方向想反。
# 下界二分:找第一个满足 cond 的位置
l, r = 0, n
while l < r:
m = (l + r) // 2
if cond(m): r = m
else: l = m + 1
return l二分答案题要先写判定函数。给我一个 x,我能不能判断 x 行不行?能判断,再谈二分。
最近未处理的问题
栈解决的是最近的、还没闭合的东西:括号、路径、表达式、下一个更大元素。单调栈尤其重要,它把很多看似要回头比较的题压成 O(n)。
识别信号
括号匹配;嵌套解码;路径回退;找右边第一个更大或更小。
常见坑
单调栈里该存下标却存值;弹栈时机不对;忘记处理栈里剩余元素。
# 单调栈:为每个元素找右边第一个更大的
stack = []
for i, x in enumerate(arr):
while stack and x > arr[stack[-1]]:
j = stack.pop()
ans[j] = i - j
stack.append(i)单调栈的本质是不变量维护。栈内元素保持单调,每个元素最多入栈出栈一次,所以总复杂度是 O(n)。