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 專家在旁邊實時給思路,容錯率高很多。