Chapter 07

自测与套路判别

前六章逐一建立了复杂度判断与六大套路——这一章不教新东西,而是逼你做最难的事:拿到陌生题面,独立判断该用哪个套路。这才是面试现场真正考的能力。

本章心智模型

  • 概念能复述 ≠ 会判别——背得出定义,不等于陌生题面能选对套路。
  • 判别靠结构信号(有序?连续子区间?最优子结构?),不是题面措辞。
  • 复杂度约束先帮你排除大半套路:先看 n 的规模,再看结构信号。

用法:先合上前面所有章节,纸笔作答,写完再展开答案。直接看答案等于把这章当复习读一遍——没有检索就没有记忆巩固。每一层的题都标了对应章节链接,仅作答错后回查之用,作答时不要先点开。

§套路判别决策树

面试现场拿到题面,判别顺序是固定的:先读约束(n 的规模),再读结构信号。约束砍掉一半分支,结构信号锁定其一。下面这张决策树把这个顺序固化成一条可背诵的脊柱。

读题:约束 n + 关键词 n≤20 且 枚举所有方案? 是 回溯 (05) 否 数组/串 + 连续子区间? 是 滑动窗口 (03) 否 有序数组 + 找两数/找值? 是 双指针 / 二分 (03 / 04) 否 求最短步数 / 层序? 是 BFS (05) 否 求最优值 + 子问题重叠? 是 DP (06) 否:求 Top-K / 动态最值 → 堆 (02)
图 7.1套路判别决策树:信号沿脊柱自上而下逐个判断,命中向右导向套路,否则进入下一个判断。注意:先看约束(n 的规模)能砍掉一半分支,再看结构信号;末端「求 Top-K / 动态最值」走堆 (02)。

§难度梯度金字塔

同一个套路,能复述定义、能讲清原理、能给陌生题面选中它,是三种递进的掌握程度。面试区分人的不是底两层,而是顶层的判别。

应用判别层 给题面选套路 原理层 说清为什么 / 复杂度 概念层 复述定义 难度递增
图 7.2难度梯度金字塔:概念层 → 原理层 → 应用判别层,逐层加难。注意:能答上层不代表答得了下层之外,判别层(红色顶端)才是面试区分点;下面三节按这三层排布。

§7.1 概念层(对应 01–02)

每题一句话即可作答,考的是定义的精确复述。合上书,逐题写出答案。

  1. Big-O 为何忽略常数与低阶项?它描述的是什么随 n 变化的趋势?
    提示:第 01 章
  2. 当约束给出 n≤10⁶,目标时间复杂度应落在哪个量级?哪个量级会超时?
    提示:第 01 章
  3. 链表与数组:按下标随机访问、在已知位置插入,二者复杂度各是多少?为何不同?
    提示:第 02 章
  4. 堆取最值(堆顶)与弹出最值,复杂度各是多少?
    提示:第 02 章
  5. 单调栈处理一个长度 n 的序列为何是均摊 O(n),而不是 O(n²)?
    提示:第 02 章
  6. 用 Python 做队列,为何选 collections.deque 而不是 list?两者头部出队的复杂度差在哪?
    提示:第 02 章

§7.2 原理层(对应 03–06)

这一层不问"是什么",问"为什么成立"。答不出原理,套路就只是背下的模板。

  1. 对撞双指针(左右向中间收)成立的前提是什么?数组无序时它为何失效?
    提示:第 03 章
  2. 滑动窗口为何是 O(n) 而非 O(n²)?两个指针各自的移动总量如何界定?
    提示:第 03 章
  3. "二分答案"能成立,依赖待求量与判定函数之间的什么关键性质?
    提示:第 04 章
  4. DFS 与 BFS 在遍历顺序上的唯一本质区别是什么?这个区别由什么数据结构造成?
    提示:第 05 章
  5. 回溯中"撤销选择"那一步的作用是什么?省掉它会导致什么后果?
    提示:第 05 章
  6. 动态规划要能用,必须同时满足哪两个前提?贪心在缺少哪个前提时会失效?
    提示:第 06 章

§7.3 应用判别层(综合 01–06)

面试真正考的一层:给一段陌生题面,独立说出用哪个套路、为什么、复杂度是多少。每题作答都要把这三点写全。方括号标注了牵涉的章节。

  1. 有序数组找两数之和等于 target。选哪个套路?为什么?复杂度?
    [牵涉 01 约束 + 03 / 02]
  2. n≤18 的数组,求所有元素和恰为 target 的子集。选哪个套路?为什么?复杂度?
    [牵涉 01 + 05]
  3. n=10⁶ 的数组,求第 k 大的元素。选哪个套路?为什么?复杂度?有哪些备选与取舍?
    [牵涉 02 堆 + 04]
  4. 无权图中求从起点到终点的最少步数。选哪个套路?为什么 DFS 不行?复杂度?
    [牵涉 05]
  5. 求一个数组的最大连续子数组和。选哪个套路?为什么?复杂度?和前缀和、分治相比如何?
    [牵涉 03 + 06]
  6. 判断单链表是否有环,若有则找出入环点。选哪个套路?为什么?空间复杂度?
    [牵涉 02 + 03]
亲手画一张图

合上教程,在纸上凭记忆画出起点页的概念地图——只画 复杂度、数据结构、四大套路族、套路判别 这几个块和它们的箭头。画完回到 index.html 的图 0 对照:你画的图里,复杂度是在顶端约束其他、还是被你画成了平级的一个套路?这个位置关系正是本教程的核心。

答案(三个层全部做完再展开)

概念层

  1. 当 n 足够大,常数与低阶项相对最高阶项可忽略;Big-O 描述的是运行时间随 n 增长的趋势量级,不是精确耗时。比较两算法谁更优,比的是量级而非常数。
  2. n≤10⁶ 时目标为 O(n) 或 O(n log n);O(n²)(约 10¹² 次操作)必然超时。这是约束反推复杂度的典型一步。
  3. 数组按下标随机访问 O(1)、任意位置插入 O(n)(要搬移后续元素);链表随机访问 O(n)(要逐个走)、已知节点处插入 O(1)(只改指针)。差异源于内存布局:数组连续、链表靠指针串联。
  4. 取堆顶最值 O(1);弹出最值 O(log n)(弹出后要下沉重建堆序)。
  5. 序列中每个元素至多入栈一次、出栈一次,总操作量与 n 成正比,故 n 次操作均摊到每步是 O(1),整体 O(n)。内层 while 弹栈的总次数被"每个元素只出栈一次"封顶。
  6. deque 两端入队出队均 O(1);list 在头部 pop(0) 是 O(n)(要整体前移)。做队列必须用 deque。

原理层

  1. 前提是数组有序:当前两数之和偏大就右指针左移(必变小)、偏小就左指针右移(必变大),每步都能确定排除一侧。无序时移动指针不能保证和的单调变化,排除逻辑不成立。
  2. 左右指针都只向右移动、各自至多走 n 步,合计移动量 O(n);窗口扩张与收缩共享这个总预算,故整体线性,而非每个右端点都重扫一遍的 O(n²)。
  3. 依赖单调性:判定函数对待求量是单调的(如"用 ≤x 的容量能否完成"随 x 单调由否变是),于是可行区间与不可行区间被一个边界分开,二分这个边界即可。
  4. 唯一本质区别是节点的处理顺序:DFS 用栈(递归即隐式栈)一路走到底再回退,BFS 用队列按距起点的层数由近及远扩展。区别完全由"栈 vs 队列"造成。
  5. 撤销选择把状态恢复到进入该分支前,使同一份路径数据能被复用去探索下一个兄弟分支。省掉它,前一分支的选择会污染后续分支,枚举出错。
  6. DP 两前提:最优子结构(大问题的最优解由子问题最优解拼成)与子问题重叠(同一子问题被反复求解,故值得记忆化)。贪心在缺少"局部最优能导向全局最优"这一性质时失效——即必须靠回溯/全局权衡才能得最优解的题,贪心会错。

应用判别层

  1. 双指针。数组已有序,对撞双指针 O(n) 时间、O(1) 空间;哈希表也能 O(n) 时间但要 O(n) 额外空间。有序这一信号让双指针在省空间上胜出。
  2. 回溯枚举 + 剪枝。n≤18 这个极小约束就是"枚举所有方案"的信号,子集数 2ⁿ 在此规模可接受。沿决策树 DFS,对每个元素选/不选,配合和超过 target 即剪枝。复杂度 O(2ⁿ) 量级。
  3. quickselect 或 堆。quickselect 平均 O(n)、最坏 O(n²);大小为 k 的堆遍历一遍 O(n log k),适合数据流;全排序 O(n log n) 做了多余的工作,偏慢。第 k 大不需要整体有序,故避免排序。
  4. BFS。无权图中 BFS 按层扩展,第一次到达终点即最短步数;DFS 先一头扎到底,首次到达不保证是最短路径。复杂度 O(V+E)。
  5. DP(Kadane)。令 dp[i] 为以 i 结尾的最大子数组和,dp[i]=max(nums[i], dp[i-1]+nums[i]),O(n) 时间、O(1) 空间。前缀和需配合"当前最小前缀"也能 O(n);分治 O(n log n) 偏慢。Kadane 最简最快。
  6. 快慢指针(Floyd)。快指针每步 2 格、慢指针 1 格,有环必相遇;相遇后将一指针移回头部、两指针同速前进,再次相遇点即入环点。时间 O(n)、空间 O(1),胜过用哈希集合记录访问节点的 O(n) 空间解法。