Goldman Sachs 的 SDE VO 不是那种「秒杀一道 Hard 就过」的面试。它的风格非常一致:从暴力解一步步优化到最优,全程要你讲清楚每一步的时间/空间复杂度。面试官人不错、会给提示,但你必须展现清晰的思路链条。
这篇把一位候选人的四轮 VO 完整复盘——每轮的真题、解法、空间优化点,以及口述结构。
四轮总览
| 轮次 | 内容 | 考察重点 |
|---|---|---|
| Round 1 | Distinct Subsequences + 原地排序 | DP + 空间优化 + 算法选型 |
| Round 2 | Maximal Square + 大文件外部排序 | 2D DP + 海量数据处理 |
| Round 3 | Maze BFS(30min)+ Splitwise LLD(30min) | 最短路 + 面向对象设计 |
| Round 4 | 系统设计 + 设计模式(1h) | 架构 + 技术选型权衡 |
Round 1:Distinct Subsequences + 原地排序
第一题:Distinct Subsequences(经典 DP)
给字符串 s 和 t,求 s 的子序列中等于 t 的个数。
先写 2D DP,再被要求优化空间复杂度。
def numDistinct(s, t):
n, m = len(s), len(t)
# dp[j] = s 的前缀里等于 t[:j] 的子序列数
dp = [0] * (m + 1)
dp[0] = 1
for i in range(1, n + 1):
# 从右往左滚动,避免覆盖本轮还要用的 dp[j-1]
for j in range(m, 0, -1):
if s[i-1] == t[j-1]:
dp[j] += dp[j-1]
return dp[m]
空间优化关键:2D 压成 1D 滚动数组时,内层循环必须从右往左,否则 dp[j-1] 会被本轮提前更新污染。这是面试官最爱追问的点。
第二题:原地排序(不用额外空间)
面试官问 quicksort 还是 heapsort,候选人选了 quicksort。关键是从暴力解逐步优化到最优,并清楚讲出时间/空间复杂度:quicksort 平均 O(n log n)、原地、最坏 O(n²)(用随机 pivot 缓解)。
Round 2:Maximal Square + 大文件外部排序
第一题:Maximal Square(2D DP)
在 0/1 矩阵里找最大全 1 正方形的面积。
def maximalSquare(matrix):
if not matrix:
return 0
n, m = len(matrix), len(matrix[0])
# dp[i][j] = 以 (i,j) 为右下角的最大正方形边长
prev = [0] * (m + 1)
best = 0
for i in range(1, n + 1):
cur = [0] * (m + 1)
for j in range(1, m + 1):
if matrix[i-1][j-1] == '1':
cur[j] = min(prev[j], cur[j-1], prev[j-1]) + 1
best = max(best, cur[j])
prev = cur
return best * best
转移核心:dp[i][j] = min(上, 左, 左上) + 1。被追问空间优化时,用滚动数组压到 O(m)。
第二题:大文件外部排序
2GB 文件、250MB 内存,如何排序?
框架:external sort——
- 分块读入,每块(约 200MB)在内存里排序后写回磁盘
- k-way merge:用最小堆管理每个 chunk 的当前元素,每次弹出最小的写入输出
import heapq
def k_way_merge(sorted_chunks):
# sorted_chunks: 多个已排序的迭代器
heap = []
iters = [iter(c) for c in sorted_chunks]
for idx, it in enumerate(iters):
v = next(it, None)
if v is not None:
heapq.heappush(heap, (v, idx))
out = []
while heap:
v, idx = heapq.heappop(heap)
out.append(v)
nv = next(iters[idx], None)
if nv is not None:
heapq.heappush(heap, (nv, idx))
return out
被问 merge 阶段细节时,强调「用最小堆管理各 chunk 当前元素」就是标准答案。
Round 3:Maze BFS + Splitwise LLD
前 30 分钟 DSA:Maze 2(BFS 求最短路 + 优化)
标准 grid BFS,注意 visited 标记 + 队列。优化点:双向 BFS 或 A*(如果给了启发函数)。
后 30 分钟 LLD:设计 Splitwise
LLD 真正考综合能力。结构化方法:
- 需求澄清:支持谁欠谁多少、分账方式(均分/按比例/按份额)、群组、结算
- 核心类设计:
class User:
def __init__(self, uid, name):
self.uid, self.name = uid, name
class Expense:
def __init__(self, paid_by, amount, split_type, splits):
self.paid_by = paid_by # User
self.amount = amount
self.split_type = split_type # EQUAL / EXACT / PERCENT
self.splits = splits # {user: share}
class BalanceSheet:
def __init__(self):
# balances[a][b] = a 欠 b 多少
self.balances = defaultdict(lambda: defaultdict(float))
def add_expense(self, expense):
share = self._compute_shares(expense)
for user, owed in share.items():
if user != expense.paid_by:
self.balances[user][expense.paid_by] += owed
- 扩展讨论:简化债务(debt simplification)、并发结算、edge case(自己付自己)。
要点:先澄清需求 → 定义类与接口 → 最后讨论扩展性与边界。时间紧,不要求写完整代码,但思路要清晰。
Round 4:系统设计 + 设计模式(最难)
1 小时分两部分,深入讨论当前项目架构 + 设计模式 / OOP 原则的应用。
- 面试官让画当前项目架构图,逐个追问技术选型理由
- 提到用消息队列解耦服务时,被追问消息丢失、重复消费怎么处理
- 消息丢失:生产者 ack + 持久化 + 重试
- 重复消费:消费端幂等(唯一 ID 去重 / 幂等表)
- 设计模式:结合项目讲 Strategy(分账方式)、Factory(对象创建)、Observer(事件通知)的真实应用,而不是背定义
备考建议
- 逐步优化的叙事:每道题先暴力、再优化、每步报复杂度——这是 GS 的核心评分维度。
- 空间优化是高频追问:DP 题准备好滚动数组的「从右往左」细节。
- LLD 要结构化:需求 → 类设计 → 接口 → 扩展性,固定四段式。
- 系统设计结合项目:消息队列的可靠性(丢失/重复)几乎必问。
FAQ
Q1:GS SDE VO 几轮? 通常 4 轮 superday,覆盖 DSA、LLD、系统设计、项目深挖。
Q2:算法题难度如何? 以 LeetCode Medium 为主,但极其看重逐步优化 + 复杂度分析,不是 AC 就行。
Q3:LLD 要写完整代码吗? 不要求。重点是类设计、关系、接口,时间不够时讲清结构即可。
Q4:系统设计会考标准题(如设计 Twitter)吗? 更常见的是深挖你简历里的项目,让你解释架构与技术选型,比标准题更难准备。
正在准备 Goldman Sachs SDE 面试?
如果你卡在「能 AC 但讲不清优化路径」「LLD 没框架」「系统设计被追问就慌」,可以聊聊看完整的面试陪跑方案:四轮真题精讲 + 限时 mock + 全程实时辅助 + 逐轮复盘。
联系方式
需要面试真题与定制备战计划?立刻联系微信 Coding0201,获取真题。
Email: [email protected] Telegram: @OAVOProxy