面试准备

LeetCode 基本功

思路不难、但很难一次写对的 Medium / Hard 题,按类别分两层。

共 326 题 · 掌握 0 · 做过 0 · 没做 326。左边的小按钮依次是「没做 · / 做过 做 / 掌握 ✓」。

题单说明

这里的「基本功题」指思路不难、但很难一次写对的题:失分点在下标和边界、状态更新顺序、特殊输入和语言 API,而不是想不到算法。题目全部是 Medium 和 Hard,按类别分组。

怎么用

  • 第一层是纯实现题(矩阵、字符串、表达式、数组原地操作、模拟、数学、设计),优先级最高;第二层是有固定模板、但模板细节容易写错的题型。
  • 先按顺序做完第一层的 A(必刷),再做第二层的 A;之后按自己最弱的类别挑 B(加练)。C(选做)是偏门题、代码量特别大的题,以及最优解依赖专门算法的题(比如 Manacher、KMP、Floyd 判圈),有空再看。
  • 每道题写完先别提交,用一个最小的例子(长度 0、1、2)手动走一遍,专门检查边界差一、该用 while 却写成 if、heapq 的用法这三类问题。
  • 每道题的讲解(题目、思路、步骤、易错点、复杂度)在题目页里。建议先自己想、写完提交之后再对照。
  • 每道题可以标「没做 / 做过 / 掌握」,状态存在网站上,换设备也在。LeetCode 上的提交记录以后会单独同步,只算最近刷的。

数据从哪来

难度、会员标记和频率来自 LeetCode 的题目列表,2026-10-08 用 Premium 账号拉取。频率是题目列表里的 Frequency 值(0 到 100 的相对值,算法没有公开;不少老题显示为 0,不代表面试不考)。NC150 标记来自 neetcode-gh/leetcode 仓库。优先级是整理时的判断,综合了频率、题目是否经典、是否偏门。

原始页面:LeetCode 基本功题单(2026-10-11 导入)。

优先级

第一层 · 矩阵遍历与填充 0 / 20 掌握

这一类怎么练

矩阵题几乎不需要新算法,考的是在二维下标上不出错:边界什么时候收缩、方向什么时候切换、原地修改时旧值会不会被提前覆盖。把下面几种常规写法各练熟一套,遇到新题基本都能套上。

常规写法

  • 边界收缩:用 top、bottom、left、right 四个变量表示还没处理的那块矩形,每处理完一条边,就把对应的边界往里收一格。它不需要 visited 数组,适合螺旋遍历(54、59)和按层处理的题(48、1914)。
  • 方向数组加转向:把四个方向写成 [(0,1),(1,0),(0,-1),(-1,0)],用一个下标表示当前方向,下一格越界或已经访问过就把下标加 1 取模,也就是右转。这种写法通用性更强,885、2326 这类起点或形状不规则的螺旋题都能直接用。
  • 对角线分组:同一条主对角线(左上到右下)上的格子 i - j 相同,同一条反对角线(右上到左下)上的格子 i + j 相同。凡是按对角线遍历或排序的题(498、1424、1329),都可以先按这个值分组。
  • 原地标记和状态编码:题目要求 O(1) 额外空间时,就借用矩阵本身存信息:要么拿第一行第一列当标记位(73),要么用额外的数值编码「旧状态加新状态」(289、723),等所有格子都判断完再统一改写。
  • 下标映射:旋转和翻转都可以写成一个坐标公式,比如顺时针旋转 90 度时 (i, j) 去到 (j, n-1-i)。先把公式推出来再写代码,比边写边想可靠得多,48 和 1861 都用得上。

常见错误

  • 边界用闭区间还是开区间没想清楚,range 的上界少写或多写 1。
  • 螺旋遍历只剩一行或一列时,没有在走回头路之前检查边界是否已经交错,导致重复输出。
  • 原地修改时先改了某个格子,后面的判断却还要用它的旧值。
  • 把行数 m 和列数 n 搞反,方阵上测不出来,非方阵一跑就越界。
  • 用 [[0] * n] * m 创建二维列表,结果每一行都是同一个列表对象,改一格会把整列都改掉;要写成 [[0] * n for _ in range(m)]。
  1. A 48. Rotate Image Medium必刷NC150 LC 通过 85
  2. A 54. Spiral Matrix Medium必刷NC150 LC 通过 84
  3. A 6. ZigZag Conversion Medium必刷 LC 通过 80
  4. A 73. Set Matrix Zeroes Medium必刷NC150 LC 通过 76
  5. A 36. Valid Sudoku Medium必刷NC150 LC 通过 73
  6. A 59. Spiral Matrix II Medium必刷 LC 通过 59
  7. A 289. Game of Life Medium必刷 LC 通过 59
  8. A 498. Diagonal Traverse Medium必刷 LC 通过 46
  9. B 1861. Rotating the Box Medium加练 76
  10. B 835. Image Overlap Medium加练 75
  11. B 723. Candy Crush Medium加练会员 69
  12. B 240. Search a 2D Matrix II Medium加练 61
  13. B 885. Spiral Matrix III Medium加练 47
  14. B 1329. Sort the Matrix Diagonally Medium加练 44
  15. B 1424. Diagonal Traverse II Medium加练 35
  16. B 2326. Spiral Matrix IV Medium加练 19
  17. C 749. Contain Virus Hard选做 88
  18. C 1914. Cyclically Rotating a Grid Medium选做 65
  19. C 37. Sudoku Solver Hard选做 60
  20. C 419. Battleships in a Board Medium选做 36

第一层 · 字符串解析与格式化 0 / 25 掌握

这一类怎么练

字符串题几乎不需要新算法,考的是把一长串规则一次写全:前导空格、正负号、溢出、空串、连续分隔符,以及循环结束时还没处理完的最后一段。动手前先把特殊情况列成清单,写完再拿清单逐条手动验证,比边写边想可靠得多。Python 里能用一两个内置函数做完的题(比如 151),面试官常会追问不用内置函数怎么写,两种都要准备。

常规写法

  • 双指针切出连续的一段:用 i 指向一组的开头,让 j 从 i 往右走到第一个不同的字符,j - i 就是这一组的长度,处理完令 i = j。这样每一组都在循环体里被完整处理一次,不需要在循环结束后再补处理最后一组。443、38 直接用它,165、68 这类按段处理的题也是同样的结构。
  • 读写双指针原地改写:题目要求原地修改时(443,以及 151 的原地版本),用读指针扫描原数据、写指针指向结果的末尾。只要能证明结果的长度永远不超过已经读过的长度,写指针就不会超过读指针,写入也就不会覆盖还没读的内容。
  • 用栈处理嵌套和回退:凡是「新来的元素可能抵消最近的一个旧元素」的结构,比如括号匹配、路径里的 ..、删除刚拼出来的子串,都用栈(stack)。Python 里直接用列表的 append 和 pop;栈里存字符还是存下标,取决于最后需不需要知道位置(1249、32 存下标,71、1910 存内容)。
  • 按固定顺序逐段校验:解析和校验类的题(8、65、468)可以把输入看成按固定顺序出现的几段,比如先空格、再符号、再数字、再小数点或指数。按这个顺序写成几个连续的循环,每一段只吃掉属于自己的字符,最后检查是否正好吃完整个字符串。规则更复杂时,可以把它画成有限状态机(finite state machine)。
  • 规范化的键和自定义比较:分组类的题(49、609、811)核心是给每个元素算一个规范形式,当作哈希表(hash table)的键,同一组的元素键相同。排序类的题(179)则需要自定义比较规则,Python 3 的 sort 不直接接受比较函数,要用 functools.cmp_to_key 包一层。
  • 中心扩展:回文题(5、647)从每个可能的中心往两边扩展,一共 2n - 1 个中心:n 个单字符中心,加上 n - 1 个两字符之间的中心。这样不用枚举所有子串,时间从 O(n³) 降到 O(n²)。

常见错误

  • 循环结束时最后一段还没处理(最后一个单词、最后一组字符、最后一个数字),结果少了一截。
  • 没有处理空串、只有空格、全是 0 这类退化输入,比如 179 返回了 '000',71 返回了空串而不是 '/'。
  • split() 和 split(' ') 行为不同:前者把连续空白当成一个分隔符并丢掉首尾空白,后者遇到连续空格会产生空串。
  • 在循环里用 s += c 拼接长字符串。CPython 有时会优化,但不能依赖,稳妥的写法是先 append 到列表,最后统一 ''.join。
  • 在 Python 里判断溢出时写成先算后比,因为 Python 整数不会溢出所以测不出错,但面试官会按 32 位整数的规则检查(8)。
  1. A 49. Group Anagrams Medium必刷NC150 91
  2. A 179. Largest Number Medium必刷 85
  3. A 443. String Compression Medium必刷 76
  4. A 68. Text Justification Hard必刷 LC 太久 72
  5. A 151. Reverse Words in a String Medium必刷 72
  6. A 8. String to Integer (atoi) Medium必刷 67
  7. A 273. Integer to English Words Hard必刷 63
  8. A 71. Simplify Path Medium必刷 59
  9. A 165. Compare Version Numbers Medium必刷 45
  10. A 271. Encode and Decode Strings Medium必刷会员NC150 44
  11. A 1249. Minimum Remove to Make Valid Parentheses Medium必刷 37
  12. B 12. Integer to Roman Medium加练 75
  13. B 32. Longest Valid Parentheses Hard加练 LC 通过 68
  14. B 38. Count and Say Medium加练 63
  15. B 468. Validate IP Address Medium加练 55
  16. B 678. Valid Parenthesis String Medium加练NC150 51
  17. B 1910. Remove All Occurrences of a Substring Medium加练 51
  18. B 811. Subdomain Visit Count Medium加练 33
  19. C 5. Longest Palindromic Substring Medium选做NC150 99
  20. C 158. Read N Characters Given Read4 II - Call multiple times Hard选做会员 73
  21. C 647. Palindromic Substrings Medium选做NC150 62
  22. C 609. Find Duplicate File in System Medium选做 46
  23. C 591. Tag Validator Hard选做 38
  24. C 65. Valid Number Hard选做 LC 太久 33
  25. C 722. Remove Comments Medium选做 28

第一层 · 表达式求值与解析 0 / 15 掌握

这一类怎么练

计算器(Basic Calculator)这一组题是基本功里最容易被追问的,面试官通常先问只有加减,再加上乘除,最后加上括号。这一类题的难点不在算法,而在扫描时的状态管理:多位数要累积,运算符要等下一个数读完才能结算,括号要保存和恢复外层的状态,字符串结束时还要补一次结算。熟练掌握「用栈保存外层状态」和「递归下降」两套写法,就能覆盖这一类的绝大多数题。

常规写法

  • 记住上一个运算符:扫描时用 op 记住当前数字前面的运算符,等这个数字完整读完之后,再根据 op 结算:加减把带符号的数压栈,乘除立刻和栈顶计算。最后把栈里的数加起来。这样乘除自然就比加减先算,适用于 227,也是 772 的基础。
  • 遇到括号就保存外层状态:遇到 ( 时,把括号外面已经算好的部分和括号前的符号压栈,然后把当前状态清零,开始算括号里面;遇到 ) 时,把括号里的结果和栈里保存的外层状态合并。224、394、726、1190 都是这个模式,区别只在于「状态」分别是部分和、已经拼好的字符串、原子计数表和已收集的字符。
  • 递归下降(recursive descent):按优先级把文法分成三层:expression 是若干个 term 用加减连接,term 是若干个 factor 用乘除连接,factor 是一个数字或者一对括号包起来的 expression。每一层写成一个函数,所有函数共享同一个读取下标。一套代码就能处理加减乘除和括号(772),优先级由调用的层次自然保证,扩展新的运算符也很直接。
  • 用栈求值后缀和前缀表达式:逆波兰表达式(Reverse Polish Notation,RPN)这类运算符写在操作数后面的形式不需要括号,也没有优先级问题:从左往右扫,数字压栈,运算符弹出两个数算完再压回去(150)。三元表达式(439)从右往左扫,布尔表达式(1106)遇到 ) 时归约,都是同一个思路。
  • 分治加记忆化:如果要求的是所有可能的计算结果,而不是某一个值(241),就按每个运算符把表达式切成左右两半,递归求出两边所有可能的值再两两组合,并用记忆化(memoization)缓存子串的结果。282 则是另一种枚举,用回溯(backtracking)尝试在每个位置插入什么运算符。

常见错误

  • 数字可能是多位数,要用 num = num * 10 + d 累积,或者用 while 把连续的数字读完;用 if 只读一位就会出错。
  • 最后一个数字后面没有运算符,触发不了结算,循环结束后要补一次,或者在循环里加上 i == n - 1 的判断。
  • 减法和除法的操作数顺序搞反;以及 Python 的 // 是向下取整,而题目要求向零截断,负数相除时结果会差 1。
  • 空格可能出现在任何位置,跳过空格的 continue 不能挡住「到达末尾时的结算」。
  • 一元负号(比如 -(2+3) 或 1-(-2))在 224 里是合法输入,只按二元运算符处理就会出错。
  1. A 394. Decode String Medium必刷 83
  2. A 224. Basic Calculator Hard必刷 76
  3. A 227. Basic Calculator II Medium必刷 69
  4. A 636. Exclusive Time of Functions Medium必刷 65
  5. A 150. Evaluate Reverse Polish Notation Medium必刷NC150 57
  6. B 772. Basic Calculator III Hard加练会员 72
  7. B 726. Number of Atoms Hard加练 71
  8. B 1190. Reverse Substrings Between Each Pair of Parentheses Medium加练 55
  9. B 241. Different Ways to Add Parentheses Medium加练 42
  10. B 1106. Parsing A Boolean Expression Hard加练 36
  11. B 856. Score of Parentheses Medium加练 2
  12. C 736. Parse Lisp Expression Hard选做 66
  13. C 282. Expression Add Operators Hard选做 60
  14. C 385. Mini Parser Medium选做 0
  15. C 439. Ternary Expression Parser Medium选做会员 0

第一层 · 数组原地操作与下标技巧 0 / 20 掌握

这一类怎么练

这一类题的数据都在一个数组里,而且常常要求原地(in-place)修改或者只用 O(1) 额外空间,所以要靠下标本身携带信息:把值放回它「应该在」的下标、用正负号做标记、用几次翻转代替整体移动、用读写双指针原地压缩。思路通常一句话就能说清,失分点在于去重放在哪里、比较带不带等号、交换时下标有没有被提前改掉。动手前先用一句话写下循环不变量(invariant),比如「nums[0:w] 是已经处理好的结果」,能避免大部分边界错误。

常规写法

  • 排序加双指针:先排序,再固定一部分数,剩下两个数用左右两个指针从两端往中间收:和偏小就右移左指针,和偏大就左移右指针。排序还让相同的值挨在一起,去重只需跳过和前一个相同的数。15、16、18 都是这个套路;11 不需要排序,但同样靠「每次移动较矮的一边不会错过最优解」这个论证把指针往里收。
  • 读写双指针:读指针 i 依次扫描每个元素,写指针 w 指向下一个该写入的位置,nums[0:w] 始终是处理好的结果。因为 w 永远不会超过 i,覆盖写入不会破坏还没读到的数据。80 是原地版本;2149、2161 写到新数组里,道理相同。
  • 原地哈希和符号标记:值域正好是 1 到 n 时,每个值 x 天然对应下标 x - 1,可以把数组本身当成哈希表用。要么把 x 交换到下标 x - 1 上(41),要么把下标 x - 1 上的数变成负数,表示「x 出现过」(442)。
  • 翻转与排列调整:求下一个或上一个排列时,只改动尽量靠右的部分:从右往左找第一个破坏单调性的位置,和右边合适的数交换,必要时再把右边反转(31、556、1053,670 是类似的贪心)。189 的轮转也用翻转完成:整体翻转一次再分段各翻转一次,就能在 O(1) 空间内把两段对调并保持各自内部的顺序。
  • 三指针划分:数组只有三类值时,用 low、mid、high 把数组分成「小、中、未知、大」四段,mid 每看一个数就把它归到对应的段,一次扫描完成(75)。324 的进阶解也是在这个划分的基础上再加一层下标映射。
  • 前缀和后缀分解:「除了自己以外」的量,可以拆成「左边所有元素的结果」和「右边所有元素的结果」两部分,正着扫一遍算前缀、倒着扫一遍算后缀再合并(238)。这样不需要除法,也不需要对每个位置重新算一遍。

常见错误

  • 排序加双指针的题,去重时和后一个数比较,或者用 if 只跳过一个重复值,会漏掉答案或者产生重复的答案。
  • 41 这类交换安置的题,把交换写成 if 只换一次,换回来的那个值就没有被继续安置,这是 if 和 while 混用的典型后果。
  • Python 的多重赋值是先算右边、再从左往右依次赋值的,写 a[i], a[a[i] - 1] = a[a[i] - 1], a[i] 时左边先改了 a[i],后面的下标就算错了,应该先把目标下标存进变量。
  • 比较带不带等号没想清楚。有重复值时,31、1053 里 < 和 <= 的一字之差就决定了结果对不对。
  • 读写双指针里拿原数组的位置(比如 nums[i - 2])做判断,可那个位置可能已经被写指针覆盖了,应该和已经写好的部分 nums[w - 2] 比较。
  1. A 15. 3 Sum Medium必刷NC150 LC 通过 94
  2. A 128. Longest Consecutive Sequence Medium必刷NC150 90
  3. A 31. Next Permutation Medium必刷 86
  4. A 238. Product of Array Except Self Medium必刷NC150 LC 通过 78
  5. A 189. Rotate Array Medium必刷 77
  6. A 41. First Missing Positive Hard必刷 75
  7. A 75. Sort Colors Medium必刷 75
  8. B 11. Container With Most Water Medium加练NC150 LC 通过 95
  9. B 18. 4 Sum Medium加练 80
  10. B 16. 3 Sum Closest Medium加练 76
  11. B 556. Next Greater Element III Medium加练 66
  12. B 80. Remove Duplicates from Sorted Array II Medium加练 LC 太久 62
  13. B 2149. Rearrange Array Elements by Sign Medium加练 59
  14. B 442. Find All Duplicates in an Array Medium加练 52
  15. B 670. Maximum Swap Medium加练 48
  16. C 287. Find the Duplicate Number Medium选做NC150 72
  17. C 229. Majority Element II Medium选做 65
  18. C 2161. Partition Array According to Given Pivot Medium选做 58
  19. C 324. Wiggle Sort II Medium选做 49
  20. C 1053. Previous Permutation With One Swap Medium选做 0

第一层 · 模拟与状态机 0 / 21 掌握

这一类怎么练

模拟题按题意一步步做就能得分,难点在于规则多、状态要按正确的顺序更新,而且原点、恰好相等、最后一个元素这类特殊情况很容易漏。动手前先确定用什么数据结构表示状态(坐标加方向、栈、队列、堆),再写清楚每一步更新的先后顺序。735、1801、1606 这类「一个新元素可能连续触发多次处理」的题必须用 while;用到堆的题正好用来巩固 heapq 是一组操作普通列表的函数这一点。

常规写法

  • 坐标加方向数组:用 (x, y) 加一个方向下标 d 表示位置和朝向,方向数组按顺时针排列,右转是 d = (d + 1) % 4,左转是 d = (d + 3) % 4。874、1041、2069 都这样写;1222、2257 则是从一个点出发沿固定方向一格一格地走,直到遇到障碍或边界。
  • 用栈处理连锁反应:新元素只会先和离它最近的旧元素发生作用,而且可能一路作用下去时,用栈(stack)存已经稳定下来的元素,新元素用 while 和栈顶反复比较,直到不再发生作用(735)。
  • 用队列模拟轮转:轮流行动、绕圈淘汰这类过程,用双端队列(deque)的「队首出、队尾进」模拟最自然。649 用「下标加 n」重新入队表示下一轮,950 和 1823 用「把队首移到队尾」模拟牌堆或圆圈的转动。
  • 用堆按优先级取事件:每一步都要取出「价格最低」「最早结束」「剩余最多」的元素时,用堆(heap)。1801 用两个堆做撮合,1606 用最小堆按结束时间释放服务器,621 的模拟写法用最大堆。Python 的 heapq 只有最小堆,需要最大堆时存负数。
  • 两遍扫描和贪心:每个位置同时受左右两侧影响时,从左往右、从右往左各扫一遍,再合并两遍的结果(135、838)。134 是一遍贪心扫描,靠「失败区间里的起点都不可能成功」这个结论一次跳过大量起点。
  • 逐层或逐轮推进:状态天然分层或分轮时,只保留当前这一层,按规则算出下一层,不必模拟时间上的细节(799 的逐行倒酒、1599 的逐轮上客)。

常见错误

  • 「一个新元素可能连续触发多次处理」时写成了 if,比如 735 的连续碰撞、1801 的连续成交、1606 的批量释放服务器,只处理一次就会漏掉后面的。
  • 把 heapq 当成类来用,写出 heapq() 或 h.push(x)。它是一组函数,堆就是一个普通列表,入堆是 heapq.heappush(h, x),堆顶是 h[0]。
  • 特殊状态漏判,比如 2069 回到原点时的朝向、457 中长度为 1 的环、134 剩余油量恰好为 0 时该不该换起点。
  • 规则题在所有检查都通过之前就修改了状态,比如 2043 的转账先扣钱、再发现目标账户无效,失败时余额已经被改坏了。
  • 左转写成 (d - 1) % 4,在 Python 里没问题,但在 Java 或 C++ 里负数取模会得到 -1;环形数组的下标计算也有同样的问题。
  1. A 621. Task Scheduler Medium必刷NC150 76
  2. A 735. Asteroid Collision Medium必刷 74
  3. A 134. Gas Station Medium必刷NC150 70
  4. A 1041. Robot Bounded In Circle Medium必刷 17
  5. B 135. Candy Hard加练 LC 太久 70
  6. B 1823. Find the Winner of the Circular Game Medium加练 62
  7. B 2043. Simple Bank System Medium加练 61
  8. B 874. Walking Robot Simulation Medium加练 56
  9. B 1801. Number of Orders in the Backlog Medium加练 55
  10. B 799. Champagne Tower Medium加练 54
  11. B 838. Push Dominoes Medium加练 43
  12. B 649. Dota2 Senate Medium加练 35
  13. B 950. Reveal Cards In Increasing Order Medium加练 32
  14. B 794. Valid Tic-Tac-Toe State Medium加练 0
  15. C 1606. Find Servers That Handled Most Number of Requests Hard选做 62
  16. C 457. Circular Array Loop Medium选做 50
  17. C 1138. Alphabet Board Path Medium选做 43
  18. C 2257. Count Unguarded Cells in the Grid Medium选做 30
  19. C 2069. Walking Robot Simulation II Medium选做 0
  20. C 1599. Maximum Profit of Operating a Centennial Wheel Medium选做 0
  21. C 1222. Queens That Can Attack the King Medium选做 0

第一层 · 数学与大数模拟 0 / 20 掌握

这一类怎么练

这一类题的公式都不难,考的是进位、符号、溢出和精度这些细节。Python 的整数没有上限,题目要求 32 位时要自己检查范围,或者用掩码模拟固定位宽;需要比较斜率或比例时,用约分后的整数对代替浮点数。很多面试官会追问「如果不能用乘法、除法或内置大整数怎么办」,所以竖式乘法和倍增减法都要能手写。

常规写法

  • 竖式模拟和进位:逐位相加或相乘时,用一个变量 carry 保存进位(carry),循环条件里要包含「还有进位」。2 和 445 是链表上的加法,43 把每一对数位的乘积直接累加到结果的第 i + j + 1 位,166 是竖式除法再加上「余数重复就开始循环」的判断。
  • 提前检查溢出:在固定宽度的整数里,乘 10 或者取负之前就要判断会不会超界,算完再检查已经晚了。7 在每次乘 10 前和 INT_MAX // 10 比较,29 特判 -2^31 / -1,50 在取负前换成更宽的类型,371 在 Python 里用 0xFFFFFFFF 掩码模拟 32 位。
  • 倍增和二进制拆分:把一个大数按二进制拆开,每一步翻倍或者减半,可以把 O(n) 次操作降到 O(log n)。50 的快速幂和 29 的倍增减法都是这个思路。
  • 按位或按段计数:不逐个枚举数字,而是按位数分段(400),或者对每一位单独统计(233),或者按因子的幂次分层计数(172),直接算出某类数字的个数。1291 则利用「答案一定是 "123456789" 的连续子串」直接枚举所有候选。
  • 用整数代替浮点:斜率、比例这类本来要做除法的量,用约分后的整数对表示并统一符号(149);166 也是全程用整数余数做长除法,而不是先算出浮点数再找规律。
  • 随机和概率构造:要证明一个随机过程是均匀的,就数清楚「等可能的执行路径」一共有多少条、每种结果各对应几条。384 的 Fisher–Yates 每一步有 i + 1 种选择,总共 n! 条路径正好对应 n! 种排列;470 用两次 rand7() 拼出 49 种等可能结果,再把多出的 9 种拒绝掉。

常见错误

  • 循环条件漏掉了「还有进位」,最高位的进位被丢掉(2、445)。
  • Python 的 // 和 % 对负数是向下取整的,-7 // 2 等于 -4,-123 % 10 等于 7,和 C++、Java 的向零取整不同,所以处理符号的题(7、29、166)要先取绝对值再计算。
  • 用 Python 写 32 位整数的题时忘了自己检查范围,或者位运算时没有加掩码,导致结果超出范围甚至死循环(7、29、371)。
  • 比较斜率时用了浮点数,或者约分后没有统一符号,同一条直线被当成了两条(149)。
  • 计数公式在边界上差一,比如 400 段内定位时忘了先把 n 减 1,60 忘了把 k 变成从 0 开始,233 漏掉 cur == 1 时的加 1。
  1. A 2. Add Two Numbers Medium必刷NC150 LC 通过 97
  2. A 1344. Angle Between Hands of a Clock Medium必刷 96
  3. A 50. Pow(x, n) Medium必刷NC150 73
  4. A 43. Multiply Strings Medium必刷NC150 61
  5. A 166. Fraction to Recurring Decimal Medium必刷 36
  6. B 7. Reverse Integer Medium加练NC150 LC 太久 93
  7. B 29. Divide Two Integers Medium加练 67
  8. B 149. Max Points on a Line Hard加练 53
  9. B 223. Rectangle Area Medium加练 52
  10. B 445. Add Two Numbers II Medium加练 LC 通过 51
  11. C 1291. Sequential Digits Medium选做 95
  12. C 204. Count Primes Medium选做 68
  13. C 371. Sum of Two Integers Medium选做NC150 64
  14. C 564. Find the Closest Palindrome Hard选做 64
  15. C 60. Permutation Sequence Hard选做 LC 太久 61
  16. C 400. Nth Digit Medium选做 59
  17. C 233. Number of Digit One Hard选做 58
  18. C 172. Factorial Trailing Zeroes Medium选做 45
  19. C 384. Shuffle an Array Medium选做 42
  20. C 470. Implement Rand10() Using Rand7() Medium选做 20

第一层 · 数据结构设计 0 / 39 掌握

这一类怎么练

设计题考的不是新算法,而是同时维护两三个数据结构,并保证每次操作之后它们彼此一致。写之前先想清楚每个结构存什么,以及它们之间必须一直成立的关系,也就是不变量(invariant),比如「哈希表里的每个 key 都对应链表里的一个节点」。然后逐个方法检查,看每一步改动有没有把这些关系维护好。容易丢分的地方是更新顺序:先删后加还是先加后删、先改下标还是先交换,顺序一反就会出现脏数据。

常规写法

  • 哈希表加双向链表:哈希表负责 O(1) 找到节点,双向链表负责 O(1) 摘下节点和按顺序插入。链表头尾各放一个哨兵节点(sentinel),插入和删除就不用判断前后是否为空。146、460、432 都是这个结构的变形,区别只在链表按什么排序。
  • 数组加下标哈希表:要 O(1) 随机取元素,就得把元素放在连续数组里;要 O(1) 删除任意元素,就用哈希表记住每个值在数组里的下标,删除时把它和数组末尾交换再 pop。380、381 是这个套路,难点在于交换后要先更新被搬动那个元素的下标。
  • 堆加延迟删除:堆(heap)不支持删除中间的元素,所以修改或删除时不去动堆,只在一个哈希表里记录每个对象当前的真实状态。取堆顶时用 while 循环把和真实状态对不上的过期条目弹掉,直到堆顶有效为止,这个技巧叫延迟删除(lazy deletion)。2034、3408、716 都靠它,1429 用队列做的是同一个思路。
  • 按时间有序的列表加二分:如果写入总是按时间递增,那么每个 key 下面的记录天然有序,直接 append 就行,查询「某个时刻之前最近的一条」或「某段时间内有几条」时用二分查找(binary search)。981、1146、3508 都是这个结构,关键是想清楚用 bisect_left 还是 bisect_right,以及要不要减 1。
  • 前缀树(Trie):每个节点存一个子节点字典和一个结尾标记,沿着字符串一个字符一个字符往下走。208 是基本实现,211 在上面加通配符的 DFS,642 在节点上额外存热度,588 把它当目录树用。
  • 迭代器把「找下一个」放在 hasNext 里:迭代器类的题(341、251、284、900)的共同做法是,让 hasNext 负责把指针推进到下一个真正存在的元素,next 只调用 hasNext 然后取值。这样空的子列表、计数为 0 的游程都在一个地方被跳过,而跳过它们必须用 while,因为可能连续有好几个空的。

常见错误

  • 交换、删除、插入几个动作的顺序错了,导致哈希表里记的下标或节点指向的已经不是原来那个元素,典型的是 380 和 381 里交换后没有先更新被搬动元素的下标。
  • 延迟删除时用 if 只检查一次堆顶,而过期条目可能连着好几个,应该用 while 一直弹到堆顶有效为止。
  • 把 Python 的 heapq 当成类来用,写成 heapq.push 或 h = heapq()。它是一组操作普通列表的函数,正确写法是 heapq.heappush(h, x),而且只有最小堆,大顶堆要存相反数。
  • 环形数组(622、641、362 的追问)里,队尾下标、取模、满和空的判断容易差一,用 head 加 count 两个变量表示状态比 head 加 tail 更不容易错。
  • 只实现了正常路径,漏了题目规定的特殊返回值,比如空结构时返回 -1 或空字符串、重复插入返回 false。
  1. A 146. LRU Cache Medium必刷NC150 97
  2. A 295. Find Median from Data Stream Hard必刷NC150 LC 通过 79
  3. A 981. Time Based Key-Value Store Medium必刷NC150 78
  4. A 380. Insert Delete GetRandom O(1) Medium必刷 LC 通过 77
  5. A 460. LFU Cache Hard必刷 76
  6. A 155. Min Stack Medium必刷NC150 71
  7. A 1146. Snapshot Array Medium必刷 63
  8. A 341. Flatten Nested List Iterator Medium必刷 59
  9. A 208. Implement Trie (Prefix Tree) Medium必刷NC150 52
  10. B 1429. First Unique Number Medium加练会员 84
  11. B 362. Design Hit Counter Medium加练会员 82
  12. B 353. Design Snake Game Medium加练会员 65
  13. B 1472. Design Browser History Medium加练 65
  14. B 3484. Design Spreadsheet Medium加练 62
  15. B 348. Design Tic-Tac-Toe Medium加练会员 61
  16. B 1396. Design Underground System Medium加练 58
  17. B 3408. Design Task Manager Medium加练 56
  18. B 622. Design Circular Queue Medium加练 54
  19. B 355. Design Twitter Medium加练NC150 54
  20. B 895. Maximum Frequency Stack Hard加练 52
  21. B 251. Flatten 2D Vector Medium加练会员 45
  22. B 211. Add and Search Word - Data structure design Medium加练NC150 39
  23. B 2034. Stock Price Fluctuation Medium加练 35
  24. B 1352. Product of the Last K Numbers Medium加练 14
  25. B 284. Peeking Iterator Medium加练 0
  26. C 588. Design In-Memory File System Hard选做会员 79
  27. C 716. Max Stack Hard选做会员 LC 太久 74
  28. C 631. Design Excel Sum Formula Hard选做会员 74
  29. C 1797. Design Authentication Manager Medium选做 72
  30. C 642. Design Search Autocomplete System Hard选做会员 67
  31. C 1166. Design File System Medium选做会员 67
  32. C 1206. Design Skiplist Hard选做 66
  33. C 635. Design Log Storage System Medium选做会员 57
  34. C 432. All O\`one Data Structure Hard选做 57
  35. C 2296. Design a Text Editor Hard选做 52
  36. C 3508. Implement Router Medium选做 52
  37. C 381. Insert Delete GetRandom O(1) - Duplicates allowed Hard选做 42
  38. C 900. RLE Iterator Medium选做 39
  39. C 641. Design Circular Deque Medium选做 18

第二层 · 二分查找的边界 0 / 20 掌握

这一类怎么练

二分查找(binary search)的思路一句话就能说清,难点全在边界:区间是闭是开、循环条件写 < 还是 <=、mid 更新时要不要加减 1,三处里任何一处和其他两处不配套,就会死循环或者差一。这一类题的解决办法是只练一种写法,把每道题都改写成「在一个单调的判断条件上,找第一个满足条件的位置」,下面所有题的步骤都用同一套左闭右开 [lo, hi) 模板来表达。每道题写完,都用长度为 1 和 2 的输入手动走一遍循环,这是检查差一错误最便宜的办法。

常规写法

  • 统一模板(左闭右开,找第一个满足条件的位置):模板只有几行:lo, hi = 下界, 上界 + 1,然后 while lo < hi:,循环里取 mid = (lo + hi) // 2,如果 ok(mid) 为真就 hi = mid,否则 lo = mid + 1,循环结束后返回 lo。它要求判断函数 ok 是单调的,也就是在搜索范围上呈现「前面一段全是假、后面一段全是真」。整个循环维护一个不变量(invariant):小于 lo 的位置都已经确认不满足,大于等于 hi 的位置都已经确认满足(或者是越界的哨兵),[lo, hi) 是还没确定的那一段。
  • 为什么这个模板不会死循环、也不会差一:因为循环条件是 lo < hi,mid 向下取整后一定落在 [lo, hi) 里,永远取不到 hi,所以访问 nums[mid] 甚至 nums[mid + 1](只要 hi <= n - 1)都不会越界。ok(mid) 为真时 mid 自己可能就是答案,所以写 hi = mid 而不是 mid - 1;为假时 mid 已经被排除,所以写 lo = mid + 1。这两种更新都会让 [lo, hi) 严格变短,循环一定结束,结束时 lo == hi,正好是第一个满足的位置;如果它等于初始的 hi,说明没有任何位置满足。
  • 怎么定初始值、怎么把别的问法改写成这个模板:lo 取答案可能的最小值,hi 取答案可能的最大值加 1;在数组下标上就是 lo = 0、hi = n。如果已知某个位置一定满足(比如 162 的最后一个下标),也可以直接把 hi 设成它,不变量仍然成立。找「最后一个满足 P 的位置」时,改成找「第一个不满足 P 的位置」再减 1;判断某个值是否存在时,先找第一个 >= target 的位置,再检查它没有越界并且等于 target。Python 的 bisect.bisect_left(a, x) 就是这个模板在 ok(i) = a[i] >= x 上的现成实现,bisect_right 对应 a[i] > x。
  • 在答案上二分(binary search on answer):题目问「满足某个限制的最小速度、最小容量、最少天数」时,往往直接求很难,但「给定一个候选答案,判断它行不行」很容易,而且候选答案越大越容易满足,这就是单调性。于是在答案的取值范围上套模板,ok(x) 用一个 O(n) 的贪心或计数来判断。875、1011、410、1482、2187 都是这个结构,区别只在检查函数怎么写。
  • 在值域上二分加计数:求「第 k 小的某个值」时,可以在值的范围上二分,ok(x) 定义为「不大于 x 的元素个数至少是 k」。这个条件对 x 单调,第一个满足的 x 就是第 k 小的值,而且它一定是真实存在的元素,因为计数只在真实元素处跳变。378、668、719 都是这样,难点转移到怎么在 O(n) 或 O(m) 时间里数出个数。
  • 不是整体有序也能二分:二分真正需要的不是「数组有序」,而是「看一眼 mid 附近,就能确定答案在哪一半」。旋转数组(33、81、153、154)靠和端点比较判断哪一半有序;峰值(162、1095)靠比较 nums[mid] 和 nums[mid + 1] 判断往哪边是上坡;540 靠成对元素的下标奇偶性。只要能把这个判断写成前假后真的 ok,就可以套同一个模板。

常见错误

  • 两种区间写法混用,比如循环写成 while lo <= hi,更新却写成 hi = mid,当 lo == hi 且 ok(mid) 为真时区间不再变小,程序死循环。左闭右开就必须配 lo < hi 和 hi = mid。
  • 写成 lo = mid(而不是 mid + 1),配合向下取整的 mid,在只剩两个元素时 mid == lo,区间不变,同样死循环。
  • 上下界没有把答案包进去,比如 1011 的下界取 1 而不是最重的包裹,检查函数在小于最重包裹时会算出错误结果;或者 hi 忘了加 1,导致「全都不满足」和「最后一个位置满足」无法区分。
  • 向上取整除法写成 p // k,少算一小时。正确写法是 (p + k - 1) // k 或 -(-p // k)。
  • 在 Java、C++ 里写 (lo + hi) / 2 可能溢出,而且负数除法向零取整会让 mid 不再落在 [lo, hi) 里,统一写成 lo + (hi - lo) / 2。Python 的 // 向下取整,没有这个问题。
  1. A 875. Koko Eating Bananas Medium必刷NC150 LC 通过 92
  2. A 33. Search in Rotated Sorted Array Medium必刷NC150 82
  3. A 162. Find Peak Element Medium必刷 76
  4. A 34. Find First and Last Position of Element in Sorted Array Medium必刷 75
  5. A 528. Random Pick with Weight Medium必刷 73
  6. A 153. Find Minimum in Rotated Sorted Array Medium必刷NC150 LC 通过 62
  7. B 410. Split Array Largest Sum Hard加练 85
  8. B 540. Single Element in a Sorted Array Medium加练 83
  9. B 1011. Capacity To Ship Packages Within D Days Medium加练 77
  10. B 658. Find K Closest Elements Medium加练 64
  11. B 74. Search a 2D Matrix Medium加练NC150 61
  12. B 81. Search in Rotated Sorted Array II Medium加练 58
  13. B 378. Kth Smallest Element in a Sorted Matrix Medium加练 39
  14. C 4. Median of Two Sorted Arrays Hard选做NC150 98
  15. C 1482. Minimum Number of Days to Make m Bouquets Medium选做 69
  16. C 154. Find Minimum in Rotated Sorted Array II Hard选做 58
  17. C 1095. Find in Mountain Array Hard选做 47
  18. C 2187. Minimum Time to Complete Trips Medium选做 47
  19. C 719. Find K-th Smallest Pair Distance Hard选做 44
  20. C 668. Kth Smallest Number in Multiplication Table Hard选做 42

第二层 · 单调栈与单调队列 0 / 17 掌握

这一类怎么练

单调栈(monotonic stack)专门解决「每个元素左边或右边第一个比它大(或小)的元素是谁」这类问题,暴力做法是对每个元素往外扫,O(n^2);单调栈让每个元素只进栈、出栈各一次,降到 O(n)。它的核心动作是新元素进来时,用 while 循环把所有破坏单调性的栈顶弹出,并在弹出的那一刻结算被弹出元素的答案,这里写成 if 是最典型的错误。单调队列(monotonic deque)是它在滑动窗口上的版本,多了一步「把已经滑出窗口的队首弹掉」。这一类题的失分点集中在三处:栈里存什么、比较时用严格还是非严格不等号、遍历结束后栈里剩下的元素怎么处理。

常规写法

  • 弹出时结算:遍历到 i 时,栈顶元素 j 如果因为 nums[i] 而被弹出,说明 i 就是 j 右边第一个比它大(或小)的元素,j 的答案在这一刻确定。弹出后新的栈顶就是 j 左边第一个比它小(或大)的元素,所以一次弹出能同时拿到左右两个边界。739、84、907 都是这个结构。
  • 栈里存下标而不是值:几乎所有题都需要计算距离或宽度(739 的天数差、84 的矩形宽度、907 的子数组个数),只存值就算不出来,而有了下标随时可以用 nums[idx] 取回值。只有像 901 那样只关心累计跨度的题,才会把值和附带信息作为二元组一起存。
  • 哨兵(sentinel):在数组首尾补一个极小值或 0,可以让遍历结束时栈里所有元素都被弹出结算,也能保证栈在弹出后永远不为空,省掉 if stack 的特判。84 在两端各补一个 0 是最经典的用法。
  • 贡献法加左右边界的严格性:求「所有子数组的最小值之和」这类问题时,换个角度,对每个元素算它作为最小值能覆盖多少个子数组,个数是左边可延伸的长度乘以右边可延伸的长度。遇到相同的值时,一边用严格小于、另一边用小于等于,才能让每个子数组只被它最左(或最右)的那个最小值计数一次。907、2104、1856 都用这个框架。
  • 单调队列维护窗口最值:滑动窗口要随时取最大值时,用 collections.deque 存下标并保持对应值递减,队首就是当前窗口最大值。每一步先从队尾弹掉所有不比新元素大的,再入队,然后检查队首是否已经滑出窗口。239、1438、862 都用这个结构,862 维护的是前缀和的单调性。
  • 带约束的贪心删除:402、316 要求删除一些字符,让剩下的序列字典序最小。做法是维护单调递增栈,当前字符比栈顶小时就弹出栈顶,但每次弹出前要检查约束是否允许:402 看剩余的删除次数,316 看栈顶字符后面还会不会再出现。

常见错误

  • 弹栈写成 if 而不是 while。新元素可能比好几个栈顶都大,if 只会弹一个,剩下的元素答案永远算不出来。
  • 比较时严格和非严格不等号用错。重复值存在时,有的题会重复计数(907),有的题会算错宽度,写之前先想清楚相等的元素应该留下还是弹出。
  • 遍历结束后栈里还有元素,它们的答案是默认值(比如 739 的 0、503 的 -1)还是需要再结算一次(84),没有处理就会漏答案。补哨兵可以统一这一步。
  • 单调队列只做了队尾的单调维护,忘了把滑出窗口的队首弹掉,或者把这两步的顺序和记录答案的时机搞乱。
  • 用 list.pop(0) 当队列用,每次是 O(n),整体退化为 O(n^2);队列要用 collections.deque 的 popleft()。
  1. A 42. Trapping Rain Water Hard必刷NC150 LC 太久 100
  2. A 239. Sliding Window Maximum Hard必刷NC150 84
  3. A 84. Largest Rectangle in Histogram Hard必刷NC150 LC 通过 82
  4. A 739. Daily Temperatures Medium必刷NC150 76
  5. A 402. Remove K Digits Medium必刷 65
  6. B 503. Next Greater Element II Medium加练 78
  7. B 907. Sum of Subarray Minimums Medium加练 74
  8. B 85. Maximal Rectangle Hard加练 72
  9. B 1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit Medium加练 LC 通过 68
  10. B 901. Online Stock Span Medium加练 66
  11. B 316. Remove Duplicate Letters Medium加练 62
  12. B 853. Car Fleet Medium加练NC150 57
  13. B 456. 132 Pattern Medium加练 56
  14. C 862. Shortest Subarray with Sum at Least K Hard选做 62
  15. C 1944. Number of Visible People in a Queue Hard选做 LC 太久 59
  16. C 2104. Sum of Subarray Ranges Medium选做 51
  17. C 1856. Maximum Subarray Min-Product Medium选做 43

第二层 · 链表指针操作 0 / 18 掌握

这一类怎么练

链表题的算法几乎都很直接,丢分的原因是指针改动的顺序错了,导致断链或成环,或者在 None 上访问 .next。两个习惯能解决大部分问题:一是统一使用哑节点(dummy node),让头节点和其他节点走同一套逻辑;二是改指针之前,先用临时变量保存所有之后还要用到的节点。复杂一点的题,先在纸上画出改动前后每个节点指向谁,再按图一行一行写代码。

常规写法

  • 哑节点(dummy node):在真正的头节点前面放一个不存数据的节点,所有「删除头节点」「在头部插入」的情况就和中间节点完全一样,不用单独写分支。最后返回 dummy.next 而不是原来的 head。19、24、82、86、92、707 都靠它简化。
  • 快慢指针(fast and slow pointers):两个指针以不同速度或不同起点前进,用来找中点(143、148)、找倒数第 n 个节点(19),以及判断有没有环并找到环的入口(142)。写的时候先想清楚循环条件是 fast and fast.next 还是 fast.next and fast.next.next,它决定了偶数长度时停在哪个中点。
  • 原地反转(in-place reversal):用 prev、cur、nxt 三个变量,每一步先存 nxt = cur.next,再令 cur.next = prev,然后整体前移。反转一段区间时,也可以用头插法(每次把后面的节点挪到区间最前面),92、25、143 都是这个操作的变形。
  • 拆成多条链再拼接:需要把节点按某种规则分组时,给每组建一条带哑节点的新链,遍历时把节点挂到对应组的尾部,最后首尾相接。86、328、725 都是这种写法,关键是最后一条链的尾部要置为 None。
  • 哈希表辅助:当需要「根据旧节点找到对应的新节点」或「根据前缀和找到之前的某个节点」时,用哈希表存节点引用。138 用它存旧节点到新节点的映射,1171 用它存前缀和到节点的映射,142 的朴素写法用它记录访问过的节点。
  • 归并(merge):两条有序链表的合并是很多题的基本零件:用一个 tail 指针,每次把较小的节点接上去。148 在它的基础上做归并排序,23 把两路合并推广成用堆做的多路归并(k-way merge)。

常见错误

  • 改指针的顺序错了,比如先写 cur.next = prev,再去取 cur.next 想找下一个节点,这时已经找不到了;改指针之前先把后面要用的节点存下来。
  • 把链表拆开或重组之后,最后一个节点的 next 没有置为 None,它还指向原来的某个节点,结果形成环,遍历时死循环。
  • 循环条件只写了 fast.next,没有先判断 fast 本身是否为空,在偶数长度或空链表上访问 None.next 报错。
  • 用了哑节点,最后却返回原来的 head;如果原头节点被删除或者被换到后面,返回的就是错的。
  • 数步数时差一,比如要停在第 k 个节点的前一个,却走了 k 步;用哑节点出发并在纸上数清楚走几步,能避免大部分这类错误。
  1. A 23. Merge k Sorted Lists Hard必刷NC150 LC 通过 75
  2. A 138. Copy List with Random Pointer Medium必刷NC150 73
  3. A 25. Reverse Nodes in k-Group Hard必刷NC150 71
  4. A 143. Reorder List Medium必刷NC150 LC 通过 70
  5. A 92. Reverse Linked List II Medium必刷 66
  6. A 19. Remove Nth Node From End of List Medium必刷NC150 LC 通过 59
  7. B 61. Rotate List Medium加练 68
  8. B 328. Odd Even Linked List Medium加练 66
  9. B 148. Sort List Medium加练 64
  10. B 24. Swap Nodes in Pairs Medium加练 59
  11. B 82. Remove Duplicates from Sorted List II Medium加练 54
  12. B 86. Partition List Medium加练 52
  13. B 430. Flatten a Multilevel Doubly Linked List Medium加练 48
  14. C 142. Linked List Cycle II Medium选做 59
  15. C 707. Design Linked List Medium选做 52
  16. C 1171. Remove Zero Sum Consecutive Nodes from Linked List Medium选做 38
  17. C 725. Split Linked List in Parts Medium选做 31
  18. C 708. Insert into a Sorted Circular Linked List Medium选做会员 0

第二层 · 区间合并与扫描线 0 / 17 掌握

这一类怎么练

区间题的算法本身都不难,第一步几乎总是排序,失分点在于按什么排序、端点相接算不算重叠、以及用什么数据结构维护「当前还在进行的区间」。扫描线(sweep line)把每个区间拆成「开始」和「结束」两个事件,按时间排序后依次处理;同一时刻的开始和结束谁先处理,取决于端点相接算不算重叠,这是最常见的边界错误。每道题动手之前,先确认题目的区间是闭区间 [s, e] 还是半开区间 [s, e)。

常规写法

  • 按起点排序后合并:按起点排序后,可能重叠的区间一定相邻,所以只要拿当前区间和结果里最后一个区间比较:起点不超过它的终点就合并,终点取两者较大值,否则另起一个。56、57、759 都是这个写法,1288 在此基础上改了排序的次关键字。
  • 按终点排序的贪心:问「最多保留多少个互不重叠的区间」或「最少用几个点刺穿所有区间」时,按终点排序,每次选终点最早的区间。理由是交换论证(exchange argument):终点越早,留给后面的空间越多,任何最优解里的第一个区间都可以换成终点最早的那个而不变差。435、452 是这一类。
  • 扫描线和差分计数:求「同一时刻最多有多少个区间重叠」时,把每个区间变成 (start, +1) 和 (end, -1) 两个事件,按时间排序后累加,过程中的最大值就是答案。坐标范围小时可以直接开差分数组(difference array),在起点加、终点减,再求前缀和。253、1094、732 都能这样做。
  • 用堆维护正在进行的区间:需要知道「当前最早结束的是哪个」时,用最小堆存结束时间。Python 的 heapq 不是类,而是一组操作普通列表的函数,写法是 heapq.heappush(h, x)、heapq.heappop(h),堆顶是 h[0];要最大堆就存相反数。253、2402、218 都用这个结构。
  • 有序结构加二分维护动态区间:区间是一个个动态加入的(729、731、715、352),每次都重新排序太慢,就维护一个按起点有序、互不相交的列表,用 bisect 找到新区间可能影响的位置,只检查或合并附近的几个区间。Python 没有内置平衡树,面试中用 list 加 bisect 即可,插入是 O(n),可以说明有 sortedcontainers.SortedList 能做到 O(log n)。

常见错误

  • 端点相接算不算重叠没有看清。56 和 57 里 [1, 2] 和 [2, 3] 要合并;435 里它们不算重叠;253 和 729 用的是半开区间,一个会议在 10 点结束,另一个 10 点开始,可以共用一个会议室。
  • 扫描线里同一时刻的事件排序错误。半开区间要先处理结束再处理开始,否则会多算一个重叠。
  • 合并时终点直接取当前区间的终点,而不是两者中的较大值,遇到 [1, 10] 包含 [2, 3] 这种情况就把区间缩短了。
  • 把 heapq 当成类来用,写成 heap = heapq() 或 heap.push(x);它是函数集合,第一个参数永远是那个列表。
  • 排序时用 a[1] - b[1] 作为 Java、C++ 的比较器,坐标接近 int 边界时相减会溢出,应该用 Integer.compare。
  1. A 56. Merge Intervals Medium必刷NC150 LC 通过 96
  2. A 253. Meeting Rooms II Medium必刷会员NC150 93
  3. A 986. Interval List Intersections Medium必刷 68
  4. A 435. Non-overlapping Intervals Medium必刷NC150 65
  5. A 57. Insert Interval Medium必刷NC150 54
  6. B 1094. Car Pooling Medium加练 67
  7. B 218. The Skyline Problem Hard加练 54
  8. B 452. Minimum Number of Arrows to Burst Balloons Medium加练 50
  9. B 729. My Calendar I Medium加练 46
  10. C 3413. Maximum Coins From K Consecutive Bags Medium选做 96
  11. C 1288. Remove Covered Intervals Medium选做 81
  12. C 759. Employee Free Time Hard选做会员 65
  13. C 715. Range Module Hard选做 64
  14. C 2402. Meeting Rooms III Hard选做 64
  15. C 732. My Calendar III Hard选做 36
  16. C 352. Data Stream as Disjoint Intervals Hard选做 33
  17. C 731. My Calendar II Medium选做 23

第二层 · 前缀和与差分 0 / 16 掌握

这一类怎么练

前缀和(prefix sum)把「任意一段区间的和」变成两个前缀和相减,差分数组(difference array)正好反过来,把「给一段区间整体加一个数」变成只修改两个端点。这类题的思路通常一眼就能看出来,失分点集中在下标偏移上:前缀和数组比原数组多一位,pre[i] 表示前 i 个元素的和,所以区间 [l, r] 的和是 pre[r + 1] - pre[l],这里一不留神就差一。另一个高频错误是哈希表(hash table)的初始值,忘了放入「空前缀」,从下标 0 开始的子数组就会被漏掉。

常规写法

  • 前缀和加哈希表计数:子数组 [j, i] 的和等于 pre[i + 1] - pre[j],所以「和为 k 的子数组有多少个」可以改写成「在当前位置之前,有多少个前缀和等于当前前缀和减 k」。一边遍历一边用哈希表统计每个前缀和出现的次数,每个位置查一次表,总共 O(n)。这个方法不要求元素非负,这正是它比滑动窗口适用面更广的地方,560、930、1248、437、1074 都是这个模板。
  • 前缀余数:题目问「和能被 k 整除」时,把前缀和换成前缀和对 k 的余数。两个前缀的余数相同,说明它们的差是 k 的倍数,也就是中间那段子数组的和能被 k 整除。974 是计数,523 是判断存在,1590 是找最短,用的都是这个等价关系。
  • 哈希表存第一次或最后一次出现的下标:求最长子数组时,哈希表里存每个前缀值第一次出现的下标,因为左端点越靠左,子数组越长(525、523);求最短子数组时则存最后一次出现的下标,因为左端点越靠右越短(1590)。这时初始值要放 {0: -1},意思是「下标 -1 处的空前缀值为 0」,这样从下标 0 开始的子数组长度 i - (-1) 才算得对。
  • 差分数组:要对很多个区间各自整体加一个数,最后只问一次结果时,对每个区间执行 diff[l] += v 和 diff[r + 1] -= v,所有操作做完后求一遍前缀和就能还原出最终数组。每次区间操作是 O(1),总复杂度 O(n + q)。差分数组要开 n + 1 长,r + 1 等于 n 时才不会越界(1109、370),二维版本则是修改矩形的四个角(2536)。
  • 二维前缀和与容斥原理:P[i][j] 表示左上角 (0, 0) 到 (i - 1, j - 1) 这块矩形的和,数组开 (m + 1) × (n + 1),第 0 行和第 0 列全是 0,这样构造和查询都不用判断边界。构造和查询都用容斥原理(inclusion–exclusion):两块矩形相加时重叠部分被算了两次,要减掉一次;两块矩形相减时重叠部分被减了两次,要加回一次(304、1314)。
  • 枚举两行,把二维压成一维:子矩阵问题可以先枚举上边界 top 和下边界 bottom,把这两行之间每一列的和压成一个长度为 n 的一维数组,子矩阵就一一对应到这个一维数组上的子数组,再套用一维的模板(1074、363)。枚举行对是 O(m²),所以总复杂度是 O(m²) 乘上一维部分的代价,行数比列数多时先转置会更快。

常见错误

  • 前缀和数组和原数组差一位,pre[i] 不包含 nums[i]。区间 [l, r] 的和写成 pre[r] - pre[l] 就少算了 nums[r]。
  • 哈希表忘了放入空前缀。计数类的题要放 {0: 1},求长度的题要放 {0: -1},两者含义不同,不能混用。
  • 计数时先把当前前缀和放进哈希表再查询,当前位置会和自己配对,相当于把长度为 0 的空子数组也算了进去,k = 0 时答案会偏大。顺序必须是先查后放。
  • 差分数组只开 n 长,diff[r + 1] 在 r = n - 1 时越界;或者题目下标从 1 开始,忘了先减 1。
  • 看到「子数组和」就想用滑动窗口,但数组里有负数时窗口和不随窗口长度单调变化,这时应该用前缀和加哈希表。
  1. A 560. Subarray Sum Equals K Medium必刷 91
  2. A 304. Range Sum Query 2D - Immutable Medium必刷 60
  3. A 1109. Corporate Flight Bookings Medium必刷 51
  4. B 525. Contiguous Array Medium加练 75
  5. B 221. Maximal Square Medium加练 63
  6. B 974. Subarray Sums Divisible by K Medium加练 60
  7. B 523. Continuous Subarray Sum Medium加练 58
  8. B 437. Path Sum III Medium加练 57
  9. B 1314. Matrix Block Sum Medium加练 54
  10. B 2536. Increment Submatrices by One Medium加练 0
  11. C 930. Binary Subarrays With Sum Medium选做 64
  12. C 1248. Count Number of Nice Subarrays Medium选做 59
  13. C 1590. Make Sum Divisible by P Medium选做 36
  14. C 1074. Number of Submatrices That Sum to Target Hard选做 28
  15. C 363. Max Sum of Rectangle No Larger Than K Hard选做 22
  16. C 370. Range Addition Medium选做会员 0

第二层 · 滑动窗口与字符串匹配 0 / 21 掌握

这一类怎么练

滑动窗口(sliding window)用左右两个指针表示当前考察的一段连续区间,右指针每次前进一步把一个新元素放进来,左指针只在需要时往前收缩,两个指针都只往右走,所以总时间是 O(n)。它成立的前提是单调性:窗口扩大时,「是否合法」只会朝一个方向变化,一旦窗口不合法,继续往右扩也救不回来,只能移动左边。这类题思路不难,失分点几乎都在三个地方:收缩写成了 if 而不是 while,答案更新的位置放错(求最长和求最短放的位置不一样),以及收缩时没有同步更新窗口里的计数。本类还收了几道面试常见的字符串 DP 和字符串匹配题(10、44、72、214、1044),作为选做备查。

常规写法

  • 可变长窗口的通用模板:外层是 for right in range(n),先把 nums[right] 加进窗口状态(计数加 1,或者和加上它),然后写 while 窗口不合法:,在循环体里把 nums[left] 从状态中减掉,再让 left += 1,while 结束时 [left, right] 一定是合法的。收缩必须用 while 而不是 if,因为新加进来的一个元素,可能需要左边连续移出好几个元素才能恢复合法,比如 3 里重复字符的上一次出现可能在窗口中间。每个元素最多进窗口一次、出窗口一次,所以虽然写成了两层循环,总时间仍然是 O(n)。
  • 在哪里更新答案:求最长的合法窗口时(3、424、904、1004、340),在 while 收缩结束之后更新答案,因为这时窗口刚恢复合法,而且是以 right 结尾的最长合法窗口。求最短的合法窗口时(76、209),条件反过来写成 while 窗口合法:,在循环体里先用当前长度更新答案、再收缩,因为每收缩一步窗口都更短,必须在它变得不合法之前记下来。统计「以 right 结尾的合法子数组有多少个」时,如果合法窗口的所有子窗口也都合法(713、992 的至多版本),收缩结束后加上 right - left + 1;如果反过来,合法窗口往左延长后仍然合法(1358),那么合法的起点是 0 到 left - 1,加上的是 left。
  • 固定长度窗口:窗口长度固定为 L 时(567、438),不需要 while 收缩:右指针每前进一步,就把下标为 right - L 的元素移出窗口,从 right >= L - 1 开始每一步检查一次窗口。检查时直接比较两个长度为 26 的计数数组是 O(26),已经够用;想做到每步 O(1),可以维护「计数已经相等的字符种类数」。30 是它的变体,窗口的单位从一个字符换成了一个等长的单词。
  • 恰好 K 个转成至多 K 个:「恰好 K 种」的窗口没有单调性:窗口里恰好 K 种元素时,左边收缩一格可能还是 K 种,也可能变成 K - 1 种,用一个 while 条件没法决定什么时候停。「至多 K 种」则是单调的,可以直接用模板数出来,而恰好 K 种的个数就等于至多 K 种减去至多 K - 1 种(992,930 和 1248 也可以这样做)。
  • 滑动窗口什么时候不适用:判断能不能用滑窗,可以问自己一句:窗口已经不合法时,左边不动、右边继续扩大,有没有可能让它重新合法?如果有可能,单调性就不存在,左指针该不该动无法判断,滑窗就不适用。最典型的情况是数组里有负数:求和为 k 的子数组时,加进一个负数会让和变小,窗口和不随长度单调变化,这时应该换成前缀和加哈希表(560、974);求和至少为 k 的最短子数组且有负数时(862),要用前缀和加单调队列(monotonic queue)。
  • 字符串 DP 与专门的匹配算法:10、44、72 是二维 DP,dp[i][j] 表示第一个串的前 i 个字符和第二个串的前 j 个字符之间的关系,数组开 (m + 1) × (n + 1),第 0 行和第 0 列代表空串,要先单独初始化,访问字符时用 s[i-1]。214 和 1044 的最优解分别依赖 KMP 和滚动哈希(rolling hash),面试里一般先写出朴素做法,再讲清专门算法的核心想法就够了。

常见错误

  • 收缩左指针写成 if 而不是 while,一次只退一格,窗口在某些时刻其实还不合法,答案却已经用它更新了。
  • 收缩时没有同步更新窗口状态。计数减到 0 之后要从哈希表里删掉(del cnt[x]),否则用 len(cnt) 统计种类数会偏大,Counter 也不会自动删除值为 0 的键。
  • 答案更新的位置放错。求最长时放在 while 之前,会用到不合法的窗口;求最短时放在 while 之后,会错过最短的那个合法窗口。
  • 窗口长度是 right - left + 1,写成 right - left 会差一;固定长度窗口里要移出的元素下标是 right - L,不是 right - L + 1。
  • 数组有负数时仍然套用滑窗,样例能过,提交就错,这时应该换成前缀和。
  1. A 3. Longest Substring Without Repeating Characters Medium必刷NC150 LC 通过 98
  2. A 424. Longest Repeating Character Replacement Medium必刷NC150 80
  3. A 76. Minimum Window Substring Hard必刷NC150 LC 通过 75
  4. A 567. Permutation in String Medium必刷NC150 67
  5. B 904. Fruit Into Baskets Medium加练 83
  6. B 1004. Max Consecutive Ones III Medium加练 75
  7. B 713. Subarray Product Less Than K Medium加练 74
  8. B 438. Find All Anagrams in a String Medium加练 73
  9. B 209. Minimum Size Subarray Sum Medium加练 71
  10. B 1658. Minimum Operations to Reduce X to Zero Medium加练 70
  11. B 992. Subarrays with K Different Integers Hard加练 63
  12. B 340. Longest Substring with At Most K Distinct Characters Medium加练会员 54
  13. C 10. Regular Expression Matching Hard选做NC150 72
  14. C 72. Edit Distance Medium选做NC150 72
  15. C 1358. Number of Substrings Containing All Three Characters Medium选做 67
  16. C 44. Wildcard Matching Hard选做 LC 太久 66
  17. C 2958. Length of Longest Subarray With at Most K Frequency Medium选做 66
  18. C 1493. Longest Subarray of 1's After Deleting One Element Medium选做 59
  19. C 214. Shortest Palindrome Hard选做 58
  20. C 1044. Longest Duplicate Substring Hard选做 58
  21. C 30. Substring with Concatenation of All Words Hard选做 57

第二层 · 树的遍历、构造与序列化 0 / 29 掌握

这一类怎么练

树的递归写法大家都熟,这一类题真正考的是三件事:能不能不用递归、用栈手写遍历;能不能从遍历序列或字符串里把树还原出来;以及递归函数的返回值到底该定义成什么。失分点大多在细节上,比如该一路压栈到底的地方写成了 if、构造时区间边界差一、把返回值和全局答案混成同一个东西。Python 的默认递归深度只有 1000,一棵退化成链的树就能触发,所以迭代写法不只是面试加分项。

常规写法

  • 迭代前序和中序遍历:迭代遍历的本质是用一个显式的栈(stack)代替递归时的调用栈。前序遍历(preorder)最简单,根先入栈,每次弹出一个节点就访问它,然后先压右孩子、再压左孩子,因为栈是后进先出,这样左孩子会先被弹出。中序遍历(inorder)要先走到最左边才能访问,所以多用一个指针 cur,while cur 时把 cur 压栈并往左走,走到空了就弹出栈顶访问,再令 cur 指向它的右孩子,外层循环的条件是 cur 非空或者栈非空。173 和 426 都直接建立在这套中序写法上,173 是最好的练习题。
  • 迭代后序遍历:后序遍历(postorder)要等左右子树都处理完才访问根,有两种常用写法。第一种是取巧,按「根、右、左」的顺序做前序(弹出访问后先压左孩子再压右孩子),把得到的序列反转就是「左、右、根」,代价是要先存下整个结果再反转。第二种是真正按后序的时机访问节点,在中序写法的基础上加一个 prev 指针记录上一个访问过的节点,只有当栈顶节点的右孩子为空或者等于 prev 时,才说明它的右子树已经处理完,可以弹出访问,否则转去处理右子树。如果访问一个节点时要用到孩子的计算结果,只有第二种写法和递归后序等价。
  • 递归返回值和全局答案:写树的递归之前,先用一句话定义「这个函数对一棵子树返回什么」,再问自己「题目要的答案是不是就等于根的返回值」。很多题两者一致,比如 236 返回子树里找到的节点,根的返回值就是答案。124 则不同,返回值必须是「从当前节点往下走的单条路径」,因为父节点只能把它接成一条链;而答案是可能在当前节点分叉的「左 + 根 + 右」,它不能往上传,只能在每个节点处拿来更新一个全局变量(Python 里用 nonlocal 或者存在 self 上)。366 和 1110 也是这种模式,返回值服务于父节点,真正的结果另外收集。
  • 按层处理的层序遍历:层序遍历(level order traversal)就是用队列做 BFS,每一层开始时先记下 size = len(q),只弹出这么多个节点,期间新入队的孩子都属于下一层。102、103、199、662、958 都是在这个骨架上改一两处,103 翻转奇数层,199 取每层最后一个,662 给节点编号,958 连空孩子也入队。863 则是先给每个节点记下父节点,把树当成无向图再做 BFS。
  • 由遍历序列还原树:前序的第一个元素和后序的最后一个元素都是根,而根在中序里的位置把序列分成左右两半,由此得到左子树的大小,就可以递归处理两边(105、106、889)。实现时先用哈希表(hash table)存中序里每个值的下标,递归时传区间边界而不是切片,时间就从 O(n²) 降到 O(n)。区间用闭区间还是开区间要事先定好,所有子区间都用左子树大小 left_size 推导,最不容易差一。
  • 序列化和反序列化:只要把空节点也写成一个标记(比如 #),单独一个前序序列就能唯一确定一棵二叉树,反序列化时按同样的「根、左、右」顺序读回来即可(297)。读回时用 iter() 加 next() 当游标,比自己维护一个共享下标更不容易出错。BST 自带大小关系,可以不写空标记、靠上下界判断位置(449);N 叉树改为记录孩子个数(428);331、1028、536 则是给定一种字符串格式,让你验证或者解析。

常见错误

  • 压左链、找最右节点、按深度弹栈这些要一直做到条件不成立的操作,写成了只执行一次的 if(173、114、1028),树稍微深一点就出错。
  • 递归修改了子树,却没有把返回值接回 node.left 或 node.right(450、1110),删除或重连在父节点那里根本没有生效。
  • 构造树时把中序下标 k 直接当前序下标用,或者左子树大小少算 1,区间一错整棵树就乱了。
  • 把返回值和全局答案混成一个变量,或者把全局最大值初始化成 0,在全是负数的树上(124)得到错误答案。
  • 在 Python 里递归处理一条上万个节点的链,超过默认递归深度 1000 会抛出 RecursionError,可以用 sys.setrecursionlimit 调大,或者改写成迭代。
  1. A 236. Lowest Common Ancestor of a Binary Tree Medium必刷 LC 通过 71
  2. A 297. Serialize and Deserialize Binary Tree Hard必刷NC150 LC 通过 68
  3. A 105. Construct Binary Tree from Preorder and Inorder Traversal Medium必刷NC150 57
  4. A 103. Binary Tree Zigzag Level Order Traversal Medium必刷 55
  5. A 173. Binary Search Tree Iterator Medium必刷 44
  6. B 863. All Nodes Distance K in Binary Tree Medium加练 73
  7. B 124. Binary Tree Maximum Path Sum Hard加练NC150 71
  8. B 987. Vertical Order Traversal of a Binary Tree Hard加练 62
  9. B 102. Binary Tree Level Order Traversal Medium加练NC150 60
  10. B 199. Binary Tree Right Side View Medium加练NC150 57
  11. B 662. Maximum Width of Binary Tree Medium加练 57
  12. B 314. Binary Tree Vertical Order Traversal Medium加练会员 55
  13. B 450. Delete Node in a BST Medium加练 42
  14. B 114. Flatten Binary Tree to Linked List Medium加练 39
  15. B 426. Convert Binary Search Tree to Sorted Doubly Linked List Medium加练会员 39
  16. B 117. Populating Next Right Pointers in Each Node II Medium加练 36
  17. B 958. Check Completeness of a Binary Tree Medium加练 LC 通过 34
  18. B 106. Construct Binary Tree from Inorder and Postorder Traversal Medium加练 30
  19. B 331. Verify Preorder Serialization of a Binary Tree Medium加练 26
  20. B 449. Serialize and Deserialize BST Medium加练 11
  21. C 428. Serialize and Deserialize N-ary Tree Hard选做会员 LC 通过 73
  22. C 1028. Recover a Tree From Preorder Traversal Hard选做 72
  23. C 545. Boundary of Binary Tree Medium选做会员 66
  24. C 1650. Lowest Common Ancestor of a Binary Tree III Medium选做会员 59
  25. C 366. Find Leaves of Binary Tree Medium选做会员 57
  26. C 1110. Delete Nodes And Return Forest Medium选做 44
  27. C 116. Populating Next Right Pointers in Each Node Medium选做 42
  28. C 889. Construct Binary Tree from Preorder and Postorder Traversal Medium选做 26
  29. C 536. Construct Binary Tree from String Medium选做会员 0

第二层 · 网格 BFS 与 DFS 0 / 28 掌握

这一类怎么练

网格题本质上是在一张隐式的图上做 BFS 或 DFS,每个格子是一个节点,相邻格子之间有边,所以骨架非常固定:方向数组、越界检查、访问标记。真正会出错的是细节和变体,比如访问标记放在出队时导致重复入队、多个起点一个个单独搜导致超时、状态里需要的不只是坐标、或者边带了权重却还在用普通 BFS。求最短步数一律用 BFS,而 Python 的递归 DFS 在 300 × 300 这种规模的网格上就可能超过默认递归深度。

常规写法

  • 方向数组和越界检查:把四个方向写成 DIRS = [(1,0),(-1,0),(0,1),(0,-1)],循环里统一计算 nr, nc = r + dr, c + dc,再用 0 <= nr < m and 0 <= nc < n 判断是否在界内,这样四个方向共用同一段代码,不会出现某个方向漏写检查的情况。需要斜着走时换成八个方向(1091、529)。越界检查一定要写在读取 grid[nr][nc] 之前,因为 Python 的负下标不会报错,而是悄悄读到网格另一侧的格子,错误很难发现。
  • 访问标记放在入队时:普通 BFS 应该在格子入队的那一刻就标记为已访问,而不是等到出队。原因是一个格子可能同时是好几个格子的邻居,如果出队时才标记,它在被弹出之前会被每个邻居各推进队列一次,队列里堆满重复的格子,时间和空间都会膨胀,按层计数时也容易出错。用堆的 Dijkstra 是例外:代价是求和的情况下(505),一个格子第一次入堆时的距离不一定最短,所以要等出堆时才确定,并跳过过期的记录;而 778 和 407 的代价是取最大值,格子第一次被发现时就已经是最优,入堆时标记也正确。
  • 多源 BFS:当问题是「每个格子到最近的某类格子有多远」(542、286、1765、1162),或者「从一组起点同时向外扩散」(994、934)时,就用多源 BFS(multi-source BFS):在 BFS 开始之前,把所有起点一起放进队列并标记,距离都记为 0,然后照常按层扩展。这等价于虚构一个超级源点连向所有起点,再从它做一次普通 BFS,所以每个格子第一次被到达时的层数,就是它到最近起点的距离。如果对每个起点各做一次 BFS,复杂度会多乘上起点的个数。
  • 从边界反向搜索:当判断条件是「和边界连通」或者「能流到边界」时,从每个格子出发分别判断会很费时,反过来从边界上的格子出发搜索一遍,就能一次标记出所有满足条件的格子。130、1254、1020 都是先把和边界相连的区域标记或者淹掉,剩下的就是被包围的部分;417 则是从两个大洋的边界分别逆流而上,再取两次结果的交集。
  • 回溯中的临时标记:79 和 212 要找的是一条路径而不是一片区域,同一个格子在一条路径上不能重复使用,但换一条路径时还可以再用。所以访问标记只对当前的递归路径有效,进入格子时把它改成 '#',四个方向都试完之后再改回原来的字母。这和普通遍历中「标记了就永远不再访问」不同,两者混用就会漏掉答案。
  • 带额外状态的 BFS 和用堆代替队列:如果同一个格子在不同情况下未来能走的路不一样,就要把这个情况放进状态里,比如 1293 的状态是 (r, c, remain),visited 也要按整个状态来记录。如果每一步的代价不全是 1,比如 505 每次滚动的距离不同、778 的代价是路径上的最大高度、407 要按当前最低的墙往里推进,就把队列换成最小堆(min-heap),按代价从小到大扩展。Python 的 heapq 是一组操作普通列表的函数,写法是 heapq.heappush(h, item) 和 heapq.heappop(h)。

常见错误

  • 访问标记放在出队时,同一个格子被多次入队,在大网格上超时,在按层计数的题里还会把层数算乱。
  • 检查行和列的范围时把 m 和 n 写反,方阵上测不出来,非方阵一跑就越界或者漏掉格子;而 grid[-1][c] 在 Python 里不会报错,问题会更隐蔽。
  • 网格里存的是字符 '1' 还是整数 1、0 和 1 哪个代表陆地,每道题都不一样(200、1254、1765),照搬上一题的判断条件就会全错。
  • 做多源 BFS 时边扫描边启动搜索,而不是先把所有起点全部入队,结果退化成单源 BFS,距离偏大。
  • 按层计时或计距离时,最后一层没有扩展出新格子也把计数加了 1,或者把起点层当成第 1 层,答案差一(994、1162)。
  1. A 200. Number of Islands Medium必刷NC150 LC 通过 94
  2. A 994. Rotting Oranges Medium必刷NC150 86
  3. A 79. Word Search Medium必刷NC150 70
  4. A 130. Surrounded Regions Medium必刷NC150 60
  5. A 417. Pacific Atlantic Water Flow Medium必刷NC150 58
  6. A 329. Longest Increasing Path in a Matrix Hard必刷NC150 57
  7. B 1091. Shortest Path in Binary Matrix Medium加练 63
  8. B 212. Word Search II Hard加练NC150 59
  9. B 695. Max Area of Island Medium加练NC150 56
  10. B 542. 01 Matrix Medium加练 55
  11. B 934. Shortest Bridge Medium加练 52
  12. B 529. Minesweeper Medium加练 48
  13. B 286. Walls and Gates Medium加练会员NC150 39
  14. B 1254. Number of Closed Islands Medium加练 37
  15. C 1391. Check if There is a Valid Path in a Grid Medium选做 71
  16. C 827. Making A Large Island Hard选做 69
  17. C 489. Robot Room Cleaner Hard选做会员 68
  18. C 407. Trapping Rain Water II Hard选做 67
  19. C 694. Number of Distinct Islands Medium选做会员 64
  20. C 1293. Shortest Path in a Grid with Obstacles Elimination Hard选做 60
  21. C 778. Swim in Rising Water Hard选做NC150 58
  22. C 317. Shortest Distance from All Buildings Hard选做会员 55
  23. C 1905. Count Sub Islands Medium选做 45
  24. C 505. The Maze II Medium选做会员 40
  25. C 1020. Number of Enclaves Medium选做 28
  26. C 1765. Map of Highest Peak Medium选做 24
  27. C 1162. As Far from Land as Possible Medium选做 LC 通过 22
  28. C 490. The Maze Medium选做会员 0