Waymo 软件工程师面试实录 2026:真实面经完整复盘
Waymo面试第一人称完整复盘:涵盖算法Coding、系统设计、Behavioral面试。还原真实面试对话、高频题目与解题思路,附准备策略与注意事项,助你高效备战Waymo技术面试。
公司:Waymo 岗位:软件工程师 (SDE) 面试形式:Virtual Onsite 结果:Pass → Offer
大家好,我是 Sam。这次分享 Waymo SDE 面试的完整经历。Waymo 的面试风格和其他 FAANG 有一个很明显的区别:题目经常带有语义模糊性,你需要自己把现实场景抽象成计算模型。而且 System Design 轮不像传统意义上的设计系统,更偏向于方法论和评估框架的讨论。如果你习惯刷 LeetCode 模板去面试,Waymo 可能会让你不适应。
Coding:看似常规,实际强调建模
Waymo 的 coding 表面上看覆盖了常见题型,但真正的考察重点往往藏在约束和 follow-up 里。
题目一:Run-Length Encoding 的随机访问
面试官:给你一个已经用 RLE 压缩的数据,比如 [(1, 3), (2, 5), (3, 2)] 表示 [1,1,1,2,2,2,2,2,3,3]。现在要实现一个 Find(p) 函数,返回解压后第 p 个位置的值(从 0 开始)。 我:这是一个前缀和 + 二分查找的问题。我先计算每个段的结束位置,然后用二分找到 p 落在哪个段。
from bisect import bisect_right
class RLEIterator:
"""
Run-Length Encoding 迭代器,支持随机访问。
内部维护一个前缀和数组,表示每个段结束时的累计位置。
通过二分查找定位目标位置所在的段。
构建: O(n),n 是段数
Find(p): O(log n)
"""
def __init__(self, encoding: list[tuple[int, int]]):
self.values = [] # 每个段的值
self.prefix_sum = [] # 前缀和(累计位置)
current_sum = 0
for value, count in encoding:
current_sum += count
self.values.append(value)
self.prefix_sum.append(current_sum)
def find(self, p: int) -> int:
"""返回解压后第 p 个位置的值(0-indexed)"""
if p < 0 or p >= self.prefix_sum[-1]:
raise IndexError(f"Position {p} out of range")
# 找到第一个 prefix_sum > p 的位置
idx = bisect_right(self.prefix_sum, p)
return self.values[idx]
def range_query(self, start: int, end: int) -> list[int]:
"""
返回解压后 [start, end] 区间的值。
优化:如果区间跨越多个段,只返回段信息,不展开。
"""
result = []
idx_start = bisect_right(self.prefix_sum, start)
idx_end = bisect_right(self.prefix_sum, end)
for i in range(idx_start, idx_end + 1):
if i == 0:
continue
val = self.values[i]
prev_sum = self.prefix_sum[i - 1]
curr_sum = self.prefix_sum[i]
actual_start = max(start, prev_sum)
actual_end = min(end, curr_sum)
result.extend([val] * (actual_end - actual_start + 1))
return result
面试官追问:
面试官:如果 encoding 很大(10^6 段),range_query 展开结果会不会很慢? 我:会的。如果区间很大,展开后的列表可能达到 10^9 级别。应该返回压缩格式而不是展开。比如返回 [(value1, count1), (value2, count2), …] 这样只包含实际跨越的段信息。 面试官:如果 encoding 是动态的,比如经常插入新段怎么办? 我:这时候前缀和数组需要频繁更新,O(n) 的更新代价很高。可以用 Fenwick Tree(树状数组)或者 Segment Tree 来维护前缀和,更新是 O(log n),查询也是 O(log n)。
题目二:Custom Sort with Linear Time
面试官:给你一个字符串,按自定义字符顺序排序。要求线性时间。 我:因为字符集有限(小写字母 26 个),可以用计数排序。先统计每个字符出现次数,然后按自定义顺序输出。
def custom_sort_string(order: str, s: str) -> str:
"""
按自定义字符顺序排序字符串。
使用计数排序,保证 O(n + k) 时间复杂度。
k 是字符集大小(26)。
"""
count = [0] * 26
for c in s:
count[ord(c) - ord('a')] += 1
result = []
# 按 order 中的顺序输出
for c in order:
idx = ord(c) - ord('a')
if count[idx] > 0:
result.append(c * count[idx])
count[idx] = 0
# 输出 order 中没有的字符
for i in range(26):
if count[i] > 0:
result.append(chr(i + ord('a')) * count[i])
return ''.join(result)
题目三:稀疏矩阵乘法
from collections import defaultdict
def multiply_sparse_matrices(A: list[list[int]],
B: list[list[int]]) -> list[list[int]]:
"""
稀疏矩阵乘法 A × B。
使用稀疏表示(只存非零元素)来加速。
普通矩阵乘法: O(m * n * p)
稀疏矩阵乘法: O(nnz(A) * avg_nnz_column(B))
其中 nnz 是非零元素数量。
"""
m, n = len(A), len(A[0])
p = len(B[0])
# 将 A 转为稀疏格式: row -> [(col, value), ...]
sparse_A = defaultdict(list)
for i in range(m):
for j in range(n):
if A[i][j] != 0:
sparse_A[i].append((j, A[i][j]))
# 将 B 转为按列的稀疏格式: col -> [(row, value), ...]
sparse_B_cols = defaultdict(list)
for j in range(p):
for i in range(n):
if B[i][j] != 0:
sparse_B_cols[j].append((i, B[i][j]))
# 计算结果
result = [[0] * p for _ in range(m)]
for i in range(m):
if i not in sparse_A:
continue
for j in range(p):
if j not in sparse_B_cols:
continue
# 只有当 A 的第 i 行和 B 的第 j 列都有非零元素时才计算
dot = 0
b_col = sparse_B_cols[j]
for a_col, a_val in sparse_A[i]:
# 查找 B 的 (a_col, j) 是否非零
for b_row, b_val in b_col:
if b_row == a_col:
dot += a_val * b_val
break
result[i][j] = dot
return result
面试官:如果矩阵非常大,比如 10^5 × 10^5,内存放不下怎么办? 我:这时候需要做分块计算。把矩阵分成固定大小的 block(比如 1024 × 1024),每次只加载需要的 block 到内存中。类似外部排序的思路。同时可以用 GPU 加速稀疏矩阵乘法,CuSPARSE 库有专门优化的实现。
Problem Solving:从模糊描述到清晰模型
Waymo 的题目经常带有语义模糊的特点。电面中面试官让我计算地图节点之间的 ETA。
面试官:你有一个地图,上面有很多路口和道路。每条道路有一个通行时间。给你起点和终点,计算最短通行时间。 我:这是一个加权有向图的最短路径问题。用 Dijkstra 算法。 面试官:好的,实现一下。
import heapq
from collections import defaultdict
def shortest_eta(graph: dict[str, list[tuple[str, float]]],
start: str,
end: str) -> float:
"""
计算两点之间的最短通行时间(ETA)。
使用 Dijkstra 算法 + 最小堆。
时间复杂度: O(E log V)
空间复杂度: O(V + E)
graph: 邻接表,{节点: [(邻居, 权重), ...]}
"""
dist = {start: 0}
heap = [(0, start)] # (距离, 节点)
visited = set()
while heap:
d, node = heapq.heappop(heap)
if node in visited:
continue
visited.add(node)
if node == end:
return d
for neighbor, weight in graph.get(node, []):
new_dist = d + weight
if neighbor not in dist or new_dist < dist[neighbor]:
dist[neighbor] = new_dist
heapq.heappush(heap, (new_dist, neighbor))
return float('inf') # 不可达
面试官:如果通行时间随时间变化(比如早晚高峰),怎么办? 我:这时候需要把时间作为状态的一部分。状态变成 (节点, 当前时间),用 Time-Dependent Dijkstra。但这样状态空间会爆炸。实际中可以分段近似:把一天分成若干时段,每个时段内通行时间固定。或者用 A* 算法加启发函数来剪枝。
ASCII 图示:
地图网络:
A ──5──► B ──3──► C
│ │ │
│ 10 │ 1 │ 2
▼ ▼ ▼
D ──2──► E ──4──► F
起点 A, 终点 F
路径比较:
A→B→C→F: 5 + 3 + 2 = 10
A→B→E→F: 5 + 1 + 4 = 10
A→D→E→F: 10 + 2 + 4 = 16
最短路径: A→B→C→F 或 A→B→E→F (都是 10)
System Design:不是设计系统
Waymo 的 system design 非常不一样。面试官给我出的题目是:如何评估自动驾驶模型。
面试官:我们训练了一个新的感知模型,如何评估它的效果是否比旧模型好? 我:我会从以下几个维度来构建评估框架:
评估框架:
┌─────────────────────────────────────────────────────┐
│ 离线评估 (Offline) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 指标设计 │ │ 数据构建 │ │ 统计显著性检验 │ │
│ │ │ │ │ │ │ │
│ │ mAP │ │ 验证集 │ │ A/B test │ │
│ │ Recall │ │ 测试集 │ │ Bootstrap CI │ │
│ │ FPS │ │ 长尾场景 │ │ Paired test │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
├─────────────────────────────────────────────────────┤
│ 仿真评估 (Simulation) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 场景覆盖 │ │ 物理引擎 │ │ 安全指标 │ │
│ │ │ │ │ │ │ │
│ │ 长尾生成 │ │ 传感器模拟│ │ 碰撞率 │ │
│ │ 压力测试 │ │ 交通模拟 │ │ 接管次数 │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
├─────────────────────────────────────────────────────┤
│ 路测评估 (On-Road) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 安全兜底 │ │ 数据采集 │ │ 合规报告 │ │
│ │ │ │ │ │ │ │
│ │ 安全员 │ │ 全量日志 │ │ 监管要求 │ │
│ │ 影子模式 │ │ 边缘案例 │ │ 公众透明度 │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
面试官:如何保证离线评估和实际路测的一致性? 我:这是一个很关键的问题。离线评估和路测之间的 gap 主要来自:
- 数据分布偏移:训练数据和真实道路的分布不一致。需要持续监控数据漂移。
- 仿真保真度:仿真环境不可能完全模拟真实物理。需要用真实数据校准仿真器。
- 长尾场景:离线数据覆盖不到的极端情况。需要通过生成对抗来补充。
所以我会建议用影子模式(Shadow Mode)来缩小 gap:新模型和旧模型同时运行,但只有旧模型的决策实际控制车辆。这样可以在真实环境中安全地对比两个模型的表现。
面试总结
成功经验
- 建模能力很重要:Waymo 的题目经常模糊,需要自己抽象成计算模型。多练习把现实问题转成算法题。
- 主动提问:不要假设题目条件,要主动问清楚输入规模、约束、边界。
- System Design 要跳出框架:Waymo 的设计题不像传统 SD,更偏向方法论。要能多角度思考。
- Engineering Thinking:贴近现实的工程思维比纯算法更重要。
面试注意事项
时间管理:每轮 45-60 分钟。Problem Solving 轮花更多时间理解题意(5-10 分钟)。
技术深度:Waymo 很看重你对复杂系统的理解。能讨论仿真与真实的 gap、评估方法论的 trade-off 会很加分。
推荐阅读
- Waymo 面试全流程指南 — Waymo 面试流程、高频题目与准备策略
- System Design 面试完全攻略 — 分布式系统设计的核心原则与高频题目
- 行为面试 STAR 故事模板 — Leadership、决策、冲突解决等高频行为问题的回答框架
💡 需要面试辅导?
如果你对准备技术面试感到迷茫,或者想要个性化的面试指导和简历优化,欢迎联系 Interview Coach Pro 获取一对一辅导服务。
👉 联系我们 获取专属面试准备方案