Google SDE VO Coding 跟别家最不一样的地方是 follow-up 会一直追下去,一道题能从简单变到复杂,考的是你能不能跟着题目的变化实时调整思路。这篇把我遇到的两道题完整复盘,包括每层 follow-up 的解题过程。

题目一:日志处理
基础题
处理一批日志,每条日志包含时间戳和消息内容,要求:
- 去重(以消息内容为唯一标识)
- 按时间戳升序排序
- 输出结果
解题思路:
用哈希表记录已出现的消息内容,去重后按时间戳排序。
def process_logs(logs):
# logs: [(timestamp, message), ...]
seen = {}
for timestamp, message in logs:
if message not in seen:
seen[message] = timestamp
# 按时间戳升序排序
result = sorted(seen.items(), key=lambda x: x[1])
return [(ts, msg) for msg, ts in result]
踩坑点:
这道题最容易被”原始顺序”误导。去重之后要按时间戳排序,不是按输入顺序输出。面试时先把排序依据跟面试官确认清楚。
Follow-up 1:保留时间戳最大的那条
去重定义变了,同样的消息内容出现多次,保留时间戳最大的那条。
def process_logs_keep_latest(logs):
seen = {}
for timestamp, message in logs:
if message not in seen or seen[message] < timestamp:
seen[message] = timestamp
result = sorted(seen.items(), key=lambda x: x[1])
return [(ts, msg) for msg, ts in result]
改动很小,只需要在更新哈希表时加一个条件判断,保留时间戳更大的那条。
Follow-up 2:实时数据流,边收边输出有序结果
数据不再是一次性给全,而是实时流入,要求边接收边输出有序结果。
思路:
用最小堆维护当前有序结果,每次新数据进来去重后插入堆,堆顶永远是时间戳最小的那条。
import heapq
class LogProcessor:
def __init__(self):
self.seen = {}
self.heap = [] # (timestamp, message)
def add_log(self, timestamp: int, message: str):
if message in self.seen:
return # 去重,直接忽略
self.seen[message] = timestamp
heapq.heappush(self.heap, (timestamp, message))
def get_next(self):
if self.heap:
return heapq.heappop(self.heap)
return None
追问:如果去重定义是保留最新的,流式处理怎么改?
堆里的旧数据要做懒删除——插入新数据时标记旧数据为无效,pop 出来的时候判断是否有效。
题目二:文字占几行
第一层:无换行符
给一个字符串和文本框宽度 width,计算文字占几行。字符串里没有换行符。
直接用总长度除以 width 向上取整:
import math
def count_lines(text: str, width: int) -> int:
if not text:
return 0
return math.ceil(len(text) / width)
比如长度 10、宽度 3,结果是 4 行。
第二层:加入 \n
字符串里有 \n,遇到换行符直接换行。
模拟显示过程,维护当前行数和当前行已放的字符数:
def count_lines_with_newline(text: str, width: int) -> int:
lines = 1
current = 0
for ch in text:
if ch == '\n':
lines += 1
current = 0
else:
current += 1
if current > width:
lines += 1
current = 1 # 当前字符放到新行
return lines
踩坑点:
换行后 current 归零,但如果是因为超出 width 换行,当前字符要放到新行,current 应该从 1 开始,不是 0。
第三层:两列表格
升级成两列表格,每行有左右两个 cell,总宽度固定,需要找到让整个表格高度最小的左右宽度分配。
思路:
枚举左列宽度,右列宽度 = 总宽度 – 左列宽度。对每种分配,分别算左右两段文字的高度,每行高度取左右最大值,找总高度最小的分配方案。
def min_table_height(left_text: str, right_text: str, total_width: int) -> int:
best = float('inf')
best_left_width = 1
for left_width in range(1, total_width):
right_width = total_width - left_width
left_height = count_lines_with_newline(left_text, left_width)
right_height = count_lines_with_newline(right_text, right_width)
# 表格高度取两列的最大值
table_height = max(left_height, right_height)
if table_height < best:
best = table_height
best_left_width = left_width
return best
追问:如果有多行呢,每行都有左右 cell?
def min_table_height_multi_row(rows: list, total_width: int) -> int:
# rows: [(left_text, right_text), ...]
best = float('inf')
for left_width in range(1, total_width):
right_width = total_width - left_width
total_height = 0
for left_text, right_text in rows:
left_h = count_lines_with_newline(left_text, left_width)
right_h = count_lines_with_newline(right_text, right_width)
total_height += max(left_h, right_h)
if total_height < best:
best = total_height
return best
Google VO coding 几个共同点:
每道题都会从简单版本开始,然后不断加限制条件。做完基础版先别急着优化,等面试官给 follow-up 再决定要不要改结构。
每层 follow-up 最好复用前面的函数,面试官在看你的代码组织能力。第三层表格题直接调用了第二层的 count_lines 函数,他很满意。遇到”排序”或”顺序”类问题,先问清楚排序依据是什么,别自己假设。
身边有两个同学同期投了 Google,一个自己准备,一个找了 ProgramHelp 做 VO 助攻。自己准备那个第二轮卡在 follow-up 没过。找了助攻那个全程顺下来,现在已经入职了。不是说自己准备一定不行,但 Google 的 follow-up 方向太难预判,有北美 CS 专家在旁边实时给思路,容错率高很多。