Databricks 研究工程师 Yuchen Jin 在 X 上写了一段话,48 小时内浏览量冲到 46.8 万:"AI 终于杀死了 LeetCode 面试。过去十年,科技公司让每个工程师刷同样的算法题,证明自己能徒手反转二叉树。而今天,最弱智的 AI 模型进去就能一次性完成整个面试。"
这不是情绪化的吐槽,而是事实陈述。LeetCode 风格的算法面试,作为硅谷十年来的标准筛选工具,正在被大语言模型从根上瓦解。
一道题的死与生
"反转二叉树"之所以成为符号,源于 2015 年 Homebrew 作者 Max Howell 发的一条推:Google 拒绝了他,理由是他不会在白板上反转二叉树——而他写出了 Mac 上最流行的包管理器。这条推本身就在质疑算法面试的有效性,只是当时没有替代方案。
现在替代方案来了,不是更好的面试题,而是 AI 直接把题面拆了。
LeetCode 热题 100、剑指 Offer、企业高频题库——这些题的核心特征是:输入明确、输出确定、解法有限。这恰好是大语言模型最擅长的领域。模型不需要"理解"二叉树,它只需要见过足够多的反转实现,就能在推理时拼出正确代码。
AI 做题的实测表现
OpenAI 的 o3 模型在 Codeforces 竞赛题上达到 ELO 2027,相当于人类顶尖选手水平。更日常的场景里,GPT-4o 和 Claude 3.5 Sonnet 对 LeetCode 中等难度题的一次通过率已经超过 80%,困难题也在 50% 以上。
这不是"辅助编程",而是直接生成完整解法。面试者只需要把题面粘贴进去,拿到答案,稍作格式调整后提交。在线面试场景下,这个过程可以在 30 秒内完成。
下面用一个最小示例来演示这件事——调用 LLM API 解决一道经典中等难度题:
import openai
# LeetCode 102: 二叉树的层序遍历
# 题面描述(直接粘贴)
problem = """
给定二叉树的根节点 root,返回其节点值的层序遍历。
(即逐层地,从左到右访问所有节点)。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:
输入:root = [1]
输出:[[1]]
示例 3:
输入:root = []
输出:[]
请用 Python 实现,函数签名为 def levelOrder(root: Optional[TreeNode]) -> List[List[int]]
"""
client = openai.OpenAI() # 需设置 OPENAI_API_KEY 环境变量
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "你是一个算法竞赛选手,直接输出可运行的 Python 代码,不要解释。"},
{"role": "user", "content": problem},
],
temperature=0.0, # 低温度保证输出稳定
)
print(response.choices[0].message.content)
运行这段代码,你会拿到一份几乎可以直接提交的答案——BFS 用队列,时间复杂度 O(n),空间复杂度 O(n),标准解法。换一道题,换一个模型,结果大同小异。
关键点不是 AI 写的代码有多优雅,而是它写得太快、太稳了。 面试官花 45 分钟评估的信号,AI 在 30 秒内就能伪造。
面试信号被噪声淹没
算法面试的本质是信号提取:从候选人的解题过程中提取"逻辑清晰、代码扎实、能处理边界"这些信号。当 AI 可以一键生成完整解法,这些信号就变成了噪声——你无法分辨答案是来自候选人的大脑还是来自模型的推理。
更深层的问题是:这些信号本身的价值也在缩水。十年前,能徒手写出红黑树插入说明候选人底层功底扎实;今天,这项技能在日常开发中几乎不被使用。LLM 和 IDE 的补全功能已经覆盖了大部分常规编码场景。
那还面什么?
信号提取的目标没变——找到能胜任工程工作的人——但提取手段必须换。几个正在被验证的方向:
1. 系统设计 + 真实约束
不是画架构图,而是给一个有真实约束的问题:现有系统每天处理 2 亿请求,p99 是 300ms,现在要加一个新特征,延迟预算只有 50ms,怎么做?这种问题没有标准答案,AI 也无法直接生成——因为约束来自具体业务上下文。
2. 改代码而非写代码
给候选人一段有 bug 的生产代码,要求定位并修复。AI 可以生成新代码,但在理解他人代码、追踪隐含依赖、在既有架构中做最小改动方面,目前表现远不如人类。
3. 协作式面试
两人结对完成一个任务,面试官观察沟通方式、任务拆解、冲突处理。AI 无法模拟人际协作中的模糊性和谈判过程。
4. 带上下文的编码
给一个真实 repo 的片段,要求在现有测试框架下实现一个功能。需要理解项目结构、命名风格、测试约定——这些上下文 AI 很难从零拼出。
下面是一个"改代码而非写代码"的面试题示例,展示这类题目为什么对 AI 更难:
# 原始代码:一个有 bug 的缓存实现
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.cache = {} # key -> value
self.order = [] # 访问顺序列表
def get(self, key: int) -> int:
if key in self.cache:
self.order.remove(key) # BUG: O(n) 操作
self.order.append(key)
return self.cache[key]
return -1
def put(self, key: int, value: int) -> None:
if key in self.cache:
self.order.remove(key) # BUG: 同上
elif len(self.cache) >= self.capacity:
oldest = self.order.pop(0) # BUG: O(n) 操作
del self.cache[oldest]
self.cache[key] = value
self.order.append(key)
# 面试任务:
# 1. 找出所有性能 bug,说明每处的复杂度问题
# 2. 用 OrderedDict 或双向链表+哈希表重写,保证 get/put 均 O(1)
# 3. 补充边界测试:capacity=0、重复 put 同 key、get 不存在的 key
# 4. 不改变公开接口(方法名、参数、返回值),只改内部实现
这道题考验的是读代码、定位问题、在约束内重构的能力。AI 可以生成一个正确的 LRU 实现,但让它分析既有代码的 bug 并在不动接口的前提下修复——准确率显著下降。原因很简单:它缺少"这段代码为什么写成这样"的上下文理解。
转型清单
如果你的团队还在用 LeetCode 风格面试,以下是务实的过渡步骤:
| 步骤 | 行动 | 注意事项 |
|---|---|---|
| 1 | 承认现状 | 算法题已无法区分人类能力和 AI 输出,继续使用只会增加误判 |
| 2 | 保留基础编码验证 | 10 分钟的简单题仍有价值——确认候选人能写代码、能跑起来,但不作为主要筛选信号 |
| 3 | 引入改代码题 | 用真实 repo 的 bug 片段,观察定位和修复过程 |
| 4 | 加系统设计约束 | 给具体数字(QPS、延迟预算、存储限制),要求在约束内做决策 |
| 5 | 观察协作过程 | 结对编程或白板讨论,评估沟通和拆解能力 |
| 6 | 降低单题权重 | 用 3-4 个不同类型的短环节替代一道 45 分钟算法题 |
LeetCode 面试的死不是坏事。它逼我们回答一个被回避了十年的问题:我们到底想从面试中知道什么? 如果答案是"能不能干活",那就该用更接近真实工作的方式去验证。反转二叉树从来不是日常工作的核心——现在连验证它的价值都没有了。