Coding Test 速查:Hot 100 + 手写 MHA / Conv

Apr 2026 · LeetCode Hot 100 · PyTorch from scratch

这页是面试前两小时复习用的,不是完整题解集。目标是快速回忆 Hot 100 的题型入口、常见模板,以及三个高频深度学习手写题:multi-head attention、1D convolution、2D convolution。

核心原则:Hot 100 不是背题号,而是背“看到题目先套哪个状态/数据结构/转移”。代码题不是炫技,而是写清楚 shape、边界、mask、stride/padding。

1. 两小时复习计划

时间 复习内容 目标
0-15 min 数组、哈希、前缀和、双指针、滑动窗口 先拿最容易出的题
15-35 min 链表、栈、堆、二分 把模板写顺
35-60 min 二叉树 DFS/BFS、BST、LCA 递归返回值要清楚
60-85 min 图 DFS/BFS、拓扑排序、并查/矩阵搜索 visited 和状态染色
85-115 min DP、回溯、贪心/区间 写出状态定义和转移
115-120 min MHA / Conv1D / Conv2D shape 口述 防止代码题卡在维度

2. Hot 100 题型速查

类型 必会题 第一反应
哈希 / set Two Sum, Longest Consecutive, Group Anagrams, Majority Element 计数、查补数、set 判断序列起点
前缀和 Subarray Sum Equals K, Path Sum III, Product Except Self prefix[cur-k],树上回溯要撤销
双指针 3Sum, Container With Most Water, Move Zeroes, Palindrome Linked List 排序后夹逼;链表找中点再反转
滑动窗口 Longest Substring Without Repeating, Find All Anagrams 右扩左缩,维护计数
链表 Reverse List, Merge Two Lists, Cycle, Detect Cycle, Remove Nth, LRU dummy、快慢指针、哈希+双向链表
Valid Parentheses, Daily Temperatures, Decode String, MinStack 单调栈存 index;辅助栈存 min
堆 / TopK Kth Largest, Top K Frequent, Merge/Meeting style heap 或 bucket;Kth largest 可 quickselect
二分 Search Rotated Array, Search Range, Matrix Search 明确哪边有序;左右边界分开写
树 DFS Max Depth, Invert Tree, Diameter, LCA, Flatten, Build Tree 递归函数返回什么先说清
BST Validate BST, Convert BST, Kth-like traversal 中序、上下界、反中序累加
图 / 矩阵 Number of Islands, Course Schedule, Word Search, Evaluate Division DFS/BFS visited;拓扑排序查环
DP 一维 House Robber, Coin Change, Word Break, LIS dp[i] 的含义先定
DP 二维 Maximal Square, Edit Distance, Unique Paths, Min Path Sum 当前格依赖左/上/左上
回溯 Generate Parentheses, Permutations, Subsets, Combination Sum choose -> dfs -> unchoose
贪心 / 区间 Jump Game, Merge Intervals, Queue Reconstruction, Meeting Rooms 排序准则最重要

3. Hot 100 解法速查表

这张表只放面试复习最有用的信息:题目入口、模板、最关键的一步。真正写代码时先说模板,再落到边界。

模板入口 一行解法
1 Two Sum 哈希表 遍历 x 时查 target-x 是否见过,存值到下标。
2 Add Two Numbers 链表模拟 双指针逐位相加,维护 carry,用 dummy 串结果。
3 Longest Substring Without Repeating 滑动窗口 右扩计数,重复时左缩到合法,更新最大长度。
4 Median of Two Sorted Arrays 二分划分 在短数组二分切分点,使左右两半数量相等且左侧都小于右侧。
5 Longest Palindromic Substring 中心扩展 枚举奇偶中心向两侧扩,保存最长区间。
10 Regular Expression Matching DP dp[i][j] 表示 s[:i] 匹配 p[:j],重点处理 * 的零次和多次。
11 Container With Most Water 双指针 左右夹逼,每次移动较短边,因为面积受短边限制。
15 3Sum 排序 + 双指针 固定第一个数,剩下区间夹逼,注意跳过重复值。
17 Letter Combinations 回溯 每位数字展开字符,路径长度等于输入长度时收集。
19 Remove Nth From End 快慢指针 dummy 起步,fast 先走 n 步,再同步走到待删前驱。
20 Valid Parentheses 左括号入栈,右括号必须匹配栈顶,最后栈空。
21 Merge Two Sorted Lists 链表归并 dummy + tail,每次接较小节点,最后接剩余链表。
22 Generate Parentheses 回溯剪枝 left < n 可放左括号,right < left 可放右括号。
23 Merge k Sorted Lists 堆 / 分治 小根堆按节点值弹出,或两两归并到一条链表。
31 Next Permutation 后缀处理 从右找下降点,和右侧刚大于它的数交换,再反转后缀。
32 Longest Valid Parentheses 栈 / DP 栈存下标并保留最后非法位置,遇到匹配时更新长度。
33 Search in Rotated Sorted Array 二分 每轮判断哪一半有序,再决定目标是否落在有序半边。
34 Find First and Last Position 二分边界 分别写 lower_bound 找第一个 target 和第一个 > target
39 Combination Sum 回溯 candidates 可重复,递归传当前 index,剩余和为 0 收集。
42 Trapping Rain Water 双指针 维护左右最大值,移动较小侧并累加可接水量。
46 Permutations 回溯 used 数组控制每个数只选一次,路径满长收集。
48 Rotate Image 原地矩阵 先沿主对角线转置,再反转每一行。
49 Group Anagrams 哈希签名 排序字符串或 26 维计数作为 key 分组。
53 Maximum Subarray Kadane cur=max(x, cur+x),答案取所有 cur 最大值。
55 Jump Game 贪心 维护当前能到的最远位置,遍历中一旦 i > far 返回 false。
56 Merge Intervals 排序 + 合并 按 start 排序,当前区间和结果尾部重叠就扩 end。
62 Unique Paths DP dp[j] += dp[j-1],每格来自上方和左方。
64 Minimum Path Sum DP 原地或一维 DP,当前格加上上方/左方较小值。
70 Climbing Stairs 斐波那契 dp[i]=dp[i-1]+dp[i-2],可用两个变量滚动。
72 Edit Distance 二维 DP dp[i][j] 表示前缀距离,增删改取最小。
75 Sort Colors 三指针 zero/i/two 扫描,0 换前面,2 换后面,1 跳过。
76 Minimum Window Substring 滑动窗口 右扩满足 need,左缩保持有效并更新最短窗口。
78 Subsets 回溯 / 位枚举 每个位置选或不选,或遍历 bitmask。
79 Word Search DFS 回溯 从匹配首字母格出发,四方向搜索,访问过的格临时标记。
84 Largest Rectangle in Histogram 单调栈 栈递增,遇到更矮柱子就弹出并以弹出高度算面积。
85 Maximal Rectangle 单调栈 + 行高 每行把连续 1 转成 histogram,套 84。
94 Binary Tree Inorder Traversal 栈 / DFS 左根右,迭代版一路压左,弹出后转右。
96 Unique Binary Search Trees DP / Catalan 枚举根 kdp[n]+=dp[k-1]*dp[n-k]
98 Validate BST 上下界 / 中序 递归带 (low, high),或中序必须严格递增。
101 Symmetric Tree 双递归 比较 left.leftright.rightleft.rightright.left
102 Binary Tree Level Order BFS 队列逐层弹出,每层记录当前队列长度。
104 Maximum Depth of Binary Tree DFS 返回 1 + max(left_depth, right_depth)
105 Build Tree from Preorder and Inorder 递归建树 preorder 首元素是根,用 inorder 下标切左右子树。
114 Flatten Binary Tree to Linked List 后序 / 前序重连 把左子树展平接到右边,再把原右子树接到左链尾部。
121 Best Time to Buy and Sell Stock 一次扫描 维护历史最低价,用当前价减最低价更新最大利润。
124 Binary Tree Maximum Path Sum DFS 返回单边最大贡献,答案用 left+root+right 更新。
128 Longest Consecutive Sequence 哈希 set 只从没有前驱的数开始向后数长度。
136 Single Number 位运算 所有数字异或,成对数字抵消,剩下单数。
139 Word Break DP + set dp[i] 表示前缀可拆,枚举切点 js[j:i]
141 Linked List Cycle 快慢指针 slow 走一步 fast 走两步,相遇则有环。
142 Linked List Cycle II 快慢指针 相遇后一个指针回 head,再同步走,相遇点是入环点。
146 LRU Cache 哈希 + 双向链表 map 定位节点,链表维护最近使用顺序,容量满删尾部。
148 Sort List 归并排序 快慢指针切半,递归排序后 merge 两条有序链。
152 Maximum Product Subarray DP 滚动 同时维护最大/最小乘积,遇负数会交换角色。
155 Min Stack 辅助栈 主栈存值,min 栈同步存当前最小值。
160 Intersection of Two Linked Lists 双指针换头 A/B 指针走到尾后切到对方头部,路径总长相同后相遇。
169 Majority Element Boyer-Moore 票数为 0 换候选,相同加一,不同减一。
198 House Robber 一维 DP dp=max(skip, rob_prev+x),两个变量滚动。
200 Number of Islands DFS/BFS 遇到陆地就 flood fill 标记整座岛,计数加一。
206 Reverse Linked List 指针反转 prev, cur 迭代,每次保存 next 后反向。
207 Course Schedule 拓扑 / 染色 DFS 入度 BFS 看能否修完,或 DFS 三色判环。
208 Implement Trie 前缀树 每个节点保存 children 和 is_end,插入/查询逐字符走。
215 Kth Largest Element 堆 / quickselect 小根堆保留 k 个,或 partition 找下标 n-k
221 Maximal Square 二维 DP 若当前为 1,边长是左/上/左上最小值加一。
226 Invert Binary Tree DFS 交换左右子树,再递归处理子树。
234 Palindrome Linked List 快慢 + 反转 找中点,反转后半段,再和前半段逐个比较。
236 Lowest Common Ancestor 后序 DFS 左右子树都命中则当前是 LCA,否则返回命中的一边。
238 Product Except Self 前后缀乘积 左乘积扫一遍,右乘积反向扫一遍,无需除法。
239 Sliding Window Maximum 单调队列 队列存下标且值递减,窗口右移时弹出过期下标。
240 Search a 2D Matrix II 右上角搜索 从右上开始,大了左移,小了下移。
279 Perfect Squares DP / BFS dp[i]=min(dp[i-j*j]+1),也可把余数当图 BFS。
283 Move Zeroes 双指针 slow 指向下一个非零位置,遍历时把非零换到前面。
287 Find the Duplicate Number 快慢指针 把数组值看成 next 指针,Floyd 找环入口。
300 Longest Increasing Subsequence 贪心 + 二分 tails[k] 存长度 k+1 的最小尾值,二分替换。
301 Remove Invalid Parentheses BFS / 回溯 BFS 按删除次数扩展,首次出现合法层即答案。
309 Best Time to Buy and Sell Stock with Cooldown 状态机 DP 维护 hold/sold/rest,sold 后一天只能到 cooldown/rest。
312 Burst Balloons 区间 DP dp[l][r] 表示开区间最大值,枚举最后戳破的气球。
322 Coin Change 完全背包 dp[amount]=min(dp[amount], dp[amount-coin]+1)
337 House Robber III 树形 DP 每个节点返回 rob/not_rob 两个值。
338 Counting Bits 位 DP bits[i]=bits[i>>1]+(i&1)
347 Top K Frequent Elements 堆 / 桶 统计频率后用小根堆保 k 个,或按频率进桶。
394 Decode String [ 保存当前字符串和重复次数,遇 ] 弹出拼接。
399 Evaluate Division 图 DFS / 并查 变量是点,比值是带权边,查询时找路径乘积。
406 Queue Reconstruction by Height 贪心排序 按身高降序、k 升序排序,再按 k 插入。
416 Partition Equal Subset Sum 0/1 背包 目标是 sum/2,倒序更新 boolean dp。
437 Path Sum III 树上前缀和 DFS 维护 prefix count,进入加一,退出撤销。
438 Find All Anagrams in a String 滑动窗口计数 固定长度窗口维护字符差异,差异为 0 记录起点。
448 Find All Numbers Disappeared 原地标记 abs(nums[i])-1 做下标,把对应位置变负。
461 Hamming Distance 位运算 x ^ y 后统计 1 的个数。
494 Target Sum 背包转化 正负号问题转成子集和 (sum+target)/2
538 Convert BST to Greater Tree 反中序 右根左遍历,累加已见更大值。
543 Diameter of Binary Tree DFS 高度 每个节点用左右高度更新直径,返回单边高度。
560 Subarray Sum Equals K 前缀和哈希 当前前缀为 cur,答案加 count[cur-k]
581 Shortest Unsorted Continuous Subarray 双扫描 从左找右侧最小破坏,从右找左侧最大破坏,确定边界。
617 Merge Two Binary Trees DFS 两树节点都在则值相加,否则返回非空节点。
621 Task Scheduler 贪心公式 / 堆 由最高频任务决定空槽,答案是 max(n, (maxFreq-1)*(cool+1)+countMax)
647 Palindromic Substrings 中心扩展 每个字符和字符间隙作中心,扩到不等为止。
739 Daily Temperatures 单调栈 栈存等待更高温的下标,遇更高温时弹出并填天数。
763 Partition Labels 贪心 记录每个字符最后位置,扫描到当前段最大 last 时切分。
138 Copy List with Random Pointer 哈希 / 织链 map 原节点到新节点,或复制节点插入原链再拆分。

4. 看到题后的选择顺序

1. 需要“连续子数组/路径和”为 K?        -> prefix sum + hashmap
2. 需要“最长/最短 substring”?          -> sliding window
3. 已排序 / 可排序后找三元组?           -> two pointers
4. 链表删除/合并/反转?                 -> dummy + pointer rewrite
5. 树题问路径/高度/祖先?               -> DFS return value
6. 矩阵连通块?                         -> BFS/DFS + visited
7. 课程/依赖/先后关系?                 -> graph + cycle detection/toposort
8. 最优值/方案数/可行性?               -> DP
9. 枚举所有方案?                       -> backtracking
10. Top K / 频率?                      -> heap / bucket / quickselect

5. DP 和回溯最容易丢分的点

DP 面试时先说四句话:

1. state: dp[i] / dp[i][j] 表示什么
2. transition: 从哪些旧状态转移
3. init: 空串、0、第一行第一列怎么设
4. answer: 返回 dp[n] 还是 max(dp)

回溯模板:

def backtrack(path, choices):
    if is_done(path):
        res.append(path.copy())
        return
    for choice in choices:
        if invalid(choice):
            continue
        path.append(choice)
        backtrack(path, next_choices)
        path.pop()

6. 手写 Multi-Head Attention

面试必须讲清楚 shape:

x:      [B, T, C]
q/k/v:  [B, T, C] -> [B, H, T, D]
scores: [B, H, T, T]
out:    [B, H, T, D] -> [B, T, C]
import math
import torch
import torch.nn as nn
import torch.nn.functional as F


class MultiHeadAttention(nn.Module):
    def __init__(self, embed_dim, num_heads, dropout=0.0, bias=True):
        super().__init__()
        assert embed_dim % num_heads == 0
        self.embed_dim = embed_dim
        self.num_heads = num_heads
        self.head_dim = embed_dim // num_heads

        self.q_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
        self.k_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
        self.v_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
        self.out_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
        self.dropout = nn.Dropout(dropout)

    def _split_heads(self, x):
        # [B, T, C] -> [B, H, T, D]
        B, T, C = x.shape
        return x.view(B, T, self.num_heads, self.head_dim).transpose(1, 2)

    def _merge_heads(self, x):
        # [B, H, T, D] -> [B, T, C]
        B, H, T, D = x.shape
        return x.transpose(1, 2).contiguous().view(B, T, H * D)

    def forward(self, x, mask=None, return_attn=False):
        # x: [B, T, C]
        q = self._split_heads(self.q_proj(x))
        k = self._split_heads(self.k_proj(x))
        v = self._split_heads(self.v_proj(x))

        scores = q @ k.transpose(-2, -1)
        scores = scores / math.sqrt(self.head_dim)

        if mask is not None:
            # bool mask: True means masked out; 0/1 mask: 0 means masked out
            if mask.dtype == torch.bool:
                scores = scores.masked_fill(mask, float("-inf"))
            else:
                scores = scores.masked_fill(mask == 0, float("-inf"))

        attn = F.softmax(scores, dim=-1)
        attn = self.dropout(attn)
        out = attn @ v
        out = self.out_proj(self._merge_heads(out))
        return (out, attn) if return_attn else out

常见追问:

7. 手写 1D Convolution

输入和权重:

x:      [B, Cin, L]
weight: [Cout, Cin, K]
out:    [B, Cout, Lout]
Lout = floor((L + 2P - D*(K-1) - 1) / S + 1)
import torch
import torch.nn.functional as F


def conv1d_naive(x, weight, bias=None, stride=1, padding=0, dilation=1):
    B, Cin, L = x.shape
    Cout, Cin_w, K = weight.shape
    assert Cin == Cin_w

    x = F.pad(x, (padding, padding))
    Lpad = x.shape[-1]
    Lout = (Lpad - dilation * (K - 1) - 1) // stride + 1
    out = x.new_zeros(B, Cout, Lout)

    for b in range(B):
        for co in range(Cout):
            for i in range(Lout):
                start = i * stride
                total = x.new_tensor(0.0)
                for ci in range(Cin):
                    for k in range(K):
                        total = total + x[b, ci, start + k * dilation] * weight[co, ci, k]
                if bias is not None:
                    total = total + bias[co]
                out[b, co, i] = total
    return out

验证:

torch.manual_seed(0)
x = torch.randn(2, 3, 12)
w = torch.randn(4, 3, 5)
b = torch.randn(4)
y = conv1d_naive(x, w, b, stride=2, padding=2)
ref = F.conv1d(x, w, b, stride=2, padding=2)
assert torch.allclose(y, ref, atol=1e-5)

8. 手写 2D Convolution

输入和权重:

x:      [B, Cin, H, W]
weight: [Cout, Cin, KH, KW]
out:    [B, Cout, Hout, Wout]
Hout = floor((H + 2PH - DH*(KH-1) - 1) / SH + 1)
Wout = floor((W + 2PW - DW*(KW-1) - 1) / SW + 1)
import torch
import torch.nn.functional as F


def _pair(v):
    return v if isinstance(v, tuple) else (v, v)


def conv2d_naive(x, weight, bias=None, stride=1, padding=0, dilation=1):
    stride_h, stride_w = _pair(stride)
    pad_h, pad_w = _pair(padding)
    dil_h, dil_w = _pair(dilation)

    B, Cin, H, W = x.shape
    Cout, Cin_w, KH, KW = weight.shape
    assert Cin == Cin_w

    x = F.pad(x, (pad_w, pad_w, pad_h, pad_h))
    Hpad, Wpad = x.shape[-2], x.shape[-1]
    Hout = (Hpad - dil_h * (KH - 1) - 1) // stride_h + 1
    Wout = (Wpad - dil_w * (KW - 1) - 1) // stride_w + 1
    out = x.new_zeros(B, Cout, Hout, Wout)

    for b in range(B):
        for co in range(Cout):
            for oh in range(Hout):
                for ow in range(Wout):
                    h0 = oh * stride_h
                    w0 = ow * stride_w
                    total = x.new_tensor(0.0)
                    for ci in range(Cin):
                        for kh in range(KH):
                            for kw in range(KW):
                                total = total + (
                                    x[b, ci, h0 + kh * dil_h, w0 + kw * dil_w]
                                    * weight[co, ci, kh, kw]
                                )
                    if bias is not None:
                        total = total + bias[co]
                    out[b, co, oh, ow] = total
    return out

验证:

torch.manual_seed(0)
x = torch.randn(2, 3, 8, 9)
w = torch.randn(4, 3, 3, 5)
b = torch.randn(4)
y = conv2d_naive(x, w, b, stride=(2, 1), padding=(1, 2))
ref = F.conv2d(x, w, b, stride=(2, 1), padding=(1, 2))
assert torch.allclose(y, ref, atol=1e-5)

9. 三个代码题的面试讲法

先说什么 最容易错
MHA shape 流、scale、mask、softmax 维度 mask 语义反了;view 前忘 contiguous
Conv1D Lout 公式、stride/padding/dilation padding 方向、kernel index
Conv2D Hout/Wout 公式、四层空间循环 + channel 累加 F.pad 参数顺序是 (left,right,top,bottom)

10. 最后五分钟速记

Hot 100:
  sum/count -> hashmap/prefix
  substring -> sliding window
  sorted pair/triple -> two pointers
  tree -> define DFS return
  graph/matrix -> visited + BFS/DFS
  optimal value -> DP state first

MHA:
  [B,T,C] -> [B,H,T,D] -> scores [B,H,T,T] -> softmax -> merge

Conv:
  output size = floor((input + 2*pad - dilation*(kernel-1) - 1)/stride + 1)