一、验证类(写完之后用)

1. 快照式跟踪(你已在用)

逐轮手算每个变量的值,适合循环轮数少、逻辑复杂的题。缺点:轮数多了容易算错、算累。

- 升级版:**表格跟踪法**。每轮一行、每个变量一列,强制对齐,比在注释里写 2/3/4/null 不容易漏

2. 极小用例(最便宜的测试集)

写完先在脑中跑这三个,90% 的边界 bug 死在这:

- 空输入:[]、""、null

- 单元素:[1] —— 最容易暴露“没进循环/进错分支”的问题

- 两个元素:最小的“有结构”的输入,比如二分的 left==mid、反转链表的一轮循环

3. 极端用例

- 全部相同 [3,3,3,3](考验重复处理,34 题的硬点)

- 升序/降序/已排好/完全逆序(33 题旋转数组的退化情况)

- target 比所有元素大/小(35 题的越界答案)

- 极限规模:n=1、n=2 之外的 n 最大(检查溢出,left+right 不加防溢出写法时)

4. 反例攻击

主动问自己“什么输入能让这段代码错?”,带敌意地审自己的代码。比如无重复字符子串里主动构造 abba 逼出 Math.max 的必要性。比顺着用例跑更有攻击性,也更容易发现 bug。

二、推导类(写代码之前/之中用)

5. 不变量(你已在用)

循环每一轮都为真的性质,正确性靠它保证而非逐轮验证。写循环前先一句话写下不变量(“prev 指向已反转前缀的头”),写完后检查每条语句是否维持它。适合一切循环:二分、双指针、dp。

6. 循环终止性检查

正确性之外单独问一句:**什么保证循环会停?** 二分死循环的本质就是“区间不再严格缩小”。每个循环找那个“严格递减/递增的量”(区间长度、left 值、curr 与 null 的距离),找不到就要警惕。

7. 语义化命名(变量即注释)

prev/curr/next 比 p1/p2、lowerBound 比 bisect,名字带语义时,不变量和注释都能少写。反过来,如果一个变量想不出好名字,通常说明你还没想清它是干嘛的。

8. 画图,尤其是“分区图”

- 链表/树:画节点和箭头,箭头是操作的对象

- 数组:画分区(二分的 <target | >=target、快排的分区、滑动窗口的窗口边界)

- 一张分区图胜过三段文字推导,之前 lowerBound 的“夹击收敛图”就是这类

三、策略类(做题习惯层面)

9. 先写伪代码/接口签名再填实现

先定函数签名、输入输出含义、特殊情况返回什么,再填逻辑。边界想清楚了再动手,返工率大幅下降。

10. 特判 vs 泛化的取舍

遇到边界问题优先问:“能改初始值/条件让它自然覆盖吗?”而不是加 if。反转链表 prev = null 消灭头节点特判、35 题 right = length 消灭越界特判,都是**用初始值吸收边界**。特判越少,越不容易漏。

11. 复杂度预判反推算法

看数据规模猜复杂度:n ≤ 20 → O(2ⁿ) 回溯;n ≤ 3000 → O(n²) dp;n ≤ 10⁵ → O(n log n);n ≤ 10⁷ → O(n)。反过来如果只想到 O(n²) 而规模是 10⁵,直接换思路,不要写完再优化。

12. 举具体例子找规律(归纳法)

dp/递推题先手算 3~4 个小例子列成序列,规律往往自己浮出来(爬楼梯、打家劫舍都是这么看出来递推式的)。比干瞪眼想转移方程高效得多。

13. 事后复盘提炼“失败模式”

每道卡壳的题记一句失败原因(就像你 33 题的“left==mid 时两个分支都不进”)。攒多了会发现错误高度聚类的 20% 模式——这比多刷 20 道新题值钱。

用法建议

不是每题全用,按题型选两三个:

| 题型 | 必用组合 |

|---|---|

| 二分/双指针/滑动窗口 | 不变量 + 极小用例 + 终止性检查 |

| 链表/树 | 画图 + 快照跟踪 |

| dp/递推 | 具体例子找规律 + 表格跟踪 |

| 滑动窗口/去重 | 反例攻击(主动构造重复/回文样输入) |

核心心法一句话:**写之前用不变量和例子想清楚,写之后用极小用例和反例攻击验证**——前半段防“写错方向”,后半段防“细节失守”,你现在已经自发掌握了快照和不变量,剩下的主要是把“极小用例”和“反例攻击”固化成肌肉记忆,这两个是性价比最高的补充。