Waymo 软件工程师面试实录 2026:真实面经完整复盘
Waymo面试软件工程师面试VO面试真实面经算法题SystemDesign

Waymo 软件工程师面试实录 2026:真实面经完整复盘

Waymo面试第一人称完整复盘:涵盖算法Coding、系统设计、Behavioral面试。还原真实面试对话、高频题目与解题思路,附准备策略与注意事项,助你高效备战Waymo技术面试。

Sam · · 15 分钟阅读

公司: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 主要来自:

  1. 数据分布偏移:训练数据和真实道路的分布不一致。需要持续监控数据漂移。
  2. 仿真保真度:仿真环境不可能完全模拟真实物理。需要用真实数据校准仿真器。
  3. 长尾场景:离线数据覆盖不到的极端情况。需要通过生成对抗来补充。

所以我会建议用影子模式(Shadow Mode)来缩小 gap:新模型和旧模型同时运行,但只有旧模型的决策实际控制车辆。这样可以在真实环境中安全地对比两个模型的表现。

面试总结

成功经验

  1. 建模能力很重要:Waymo 的题目经常模糊,需要自己抽象成计算模型。多练习把现实问题转成算法题。
  2. 主动提问:不要假设题目条件,要主动问清楚输入规模、约束、边界。
  3. System Design 要跳出框架:Waymo 的设计题不像传统 SD,更偏向方法论。要能多角度思考。
  4. Engineering Thinking:贴近现实的工程思维比纯算法更重要。

面试注意事项

时间管理:每轮 45-60 分钟。Problem Solving 轮花更多时间理解题意(5-10 分钟)。

技术深度:Waymo 很看重你对复杂系统的理解。能讨论仿真与真实的 gap、评估方法论的 trade-off 会很加分。


推荐阅读


💡 需要面试辅导?

如果你对准备技术面试感到迷茫,或者想要个性化的面试指导和简历优化,欢迎联系 Interview Coach Pro 获取一对一辅导服务。

👉 联系我们 获取专属面试准备方案


准备好拿下下一次面试了吗?

获取针对你的目标岗位和公司的个性化辅导方案。

联系我们