← 返回部落格列表 Pinterest VO 四輪復盤:BQ + Coding + 系統設計
Pinterest

Pinterest VO 四輪復盤:BQ + Coding + 系統設計

2026-08-10

這次 Pinterest 的 VO 一共四輪,從 BQ 行為面到兩道 Coding、再到兩道系統設計,節奏排得很滿,最後順利拿到了 offer。這篇復盤把每一輪的真實題目、我的思路和程式碼都整理出來,如果你也在準備 Pinterest 的 onsite,希望這份筆記能幫你把每一輪的重點抓準。需要 VO 輔助或 VO 代面的同學,也可以直接參考文末的聯絡方式。

面試環境與形式概覽

四輪都透過視訊會議進行,Coding 用共享編輯器即時手寫,系統設計在白板工具上邊畫邊講。整體感受是:BQ 輪很看重你能不能用資料把 tradeoff 講清楚,Coding 輪偏重「分步演進 + 邊界澄清」,系統設計輪則更在意你有沒有先確認需求再動手設計。

輪次 形式 考點
Round 1 · BQ 行為面 + 追問 STAR 結構、複雜系統簡化、UX 與業務指標的 tradeoff
Round 2 · Coding 即時手寫 + 講解 棋子可達位置,三步演進、方向表 + 障礙物 HashSet
Round 3 · System Design 白板 + 講解 串流 join + 分組聚合看板、目錄批次上傳架構
Round 4 · Coding 即時手寫 + 講解 打亂車票的行程重建、Map + Set 找起點

節奏建議:Coding 輪先把 clarify 做足再寫程式碼,系統設計輪切忌一上來就選型,先確認讀寫模式再定方案。


Round 1:BQ 行為面

題目

這一輪問了兩個問題:

  1. 講一次你把一個複雜系統變簡單的經歷。
  2. 當使用者體驗和業務指標發生衝突時,你怎麼做取捨?

我的回答(STAR)

第一題我用了最佳化推薦 feed 去重服務的經歷。

第二題我強調的是:UX 和業務指標衝突時,先量化兩邊的影響,再看這個決策是短期還是長期。舉例說,某個改動可能短期拉高點擊但傷害長期留存,我會用 A/B 的分層指標把「短期收益」和「長期體驗成本」放在一起比,再和產品一起決定權衡點,而不是只盯單一指標。

復盤要點

BQ 輪最加分的一點,是能把抽象的「我最佳化了系統」落到具體數字上。提前準備好改造前後的對比圖,被追問 tradeoff 時才有據可依。


Round 2:Coding —— 棋子可達位置

面試官是印度裔,口音比較重,但溝通完全沒問題。題目分三步演進。

Step 1:只考慮 Queen

給定起點和棋盤大小,Queen 可以沿上下、左右和四條對角線共 8 個方向移動,把所有落在棋盤內的位置都加進結果。

Step 2:加入障礙物

用一個 HashSet 存障礙物座標。沿某個方向掃描時,一旦碰到障礙物立即停止:Queen 既不能落在障礙物上,也不能越過它。

Step 3:支援多種棋子

透過 piece_type 參數支援不同棋子:Queen 用 8 個方向,Bishop 只用 4 條對角線,Rook 只用上下左右。邊界檢查、障礙物檢查、方向掃描共用同一套程式碼路徑,只是方向表不同。

Python 解法

from typing import List, Set, Tuple

# 每種棋子對應的方向表
DIRECTIONS = {
    "queen": [(-1, 0), (1, 0), (0, -1), (0, 1),
              (-1, -1), (-1, 1), (1, -1), (1, 1)],
    "bishop": [(-1, -1), (-1, 1), (1, -1), (1, 1)],
    "rook": [(-1, 0), (1, 0), (0, -1), (0, 1)],
}


def reachable_positions(
    start: Tuple[int, int],
    n: int,
    piece_type: str,
    obstacles: Set[Tuple[int, int]],
) -> List[Tuple[int, int]]:
    """計算棋子從 start 出發在 n x n 棋盤上可達的所有位置。

    沿棋子對應的每個方向逐格前進,越界或撞到障礙物就停下。
    """
    directions = DIRECTIONS[piece_type]
    result: List[Tuple[int, int]] = []
    r0, c0 = start

    for dr, dc in directions:
        r, c = r0 + dr, c0 + dc
        # 沿該方向一直走,直到越界或遇到障礙物
        while 0 <= r < n and 0 <= c < n:
            if (r, c) in obstacles:
                break  # 障礙物不能落也不能越過
            result.append((r, c))
            r += dr
            c += dc

    return result

時間複雜度:O(n + b),n 為棋盤邊長(每個方向最多走 n 步,方向數為常數),b 為障礙物數量(建構 HashSet 的開銷)。 空間複雜度:O(b),用於存放障礙物集合。

復盤要點

三步演進的關鍵,是讓障礙物檢查和方向掃描共用一條路徑——加障礙物時只在 while 裡補一個 break,加棋子類型時只換方向表,主體邏輯不動。面試官很看重這種「加需求不推翻結構」的寫法。


Round 3:System Design —— 兩道設計題

設計題 1:廣告事件聚合看板

已有兩個服務分別產出廣告事件(adId, servingId)和使用者事件(servingId, action 類型 click / impression, region)。要做一個按廣告、action 類型和 region 聚合的看板。

本質是一次串流 join + 分組聚合:廣告事件先建立 servingId -> adId 的映射,使用者事件透過 servingId 補上 adId,再按 (adId, action, region) 聚合。

架構選型:

要主動講的點:servingId 映射的 TTL 怎麼定、事件亂序 / 遲到怎麼用 watermark 處理、聚合是滾動視窗還是滑動視窗。

設計題 2:商品目錄批次上傳

商家批次上傳目錄(價格、庫存等),單個任務最多 50 萬條商品。

我一開始的想法是讓客戶端 / SDK 把資料切成每 100 行一批推上來,但很快意識到:這把可靠性和任務管理的責任都壓到了客戶端,不理想。

更好的方案:

  1. 商家把大檔案直接上傳到物件儲存,伺服端回傳一個 job ID
  2. 訊息佇列非同步驅動解析、校驗、分片、批次寫入。
  3. 客戶端憑 job ID 輪詢進度和失敗詳情,並且只需要對失敗的部分做重試。

這樣客戶端只負責上傳和輪詢,重活都在伺服端非同步完成,可靠性和可觀測性都更好。

踩坑教訓:不要在確認讀取模式之前就選一個寫最佳化的資料庫。應該先確認目錄的讀取頻率、查詢模式、更新占比、一致性要求,再決定資料庫 / 索引 / 快取怎麼配。


Round 4:Coding —— 行程重建

給定一組打亂順序的車票,每張票有起點和終點,所有票恰好串成一條完整路線,要求還原出正確的行程順序。

思路

用一個 origin -> destination 的 Map,再用一個 Set 存所有終點。沒有出現在終點集合裡的起點就是整條路線的起點,然後順著 Map 一站一站往下接即可。

Python 解法

from typing import Dict, List, Set, Tuple


def reconstruct_itinerary(
    tickets: List[Tuple[str, str]]
) -> List[str]:
    """從打亂的車票中還原完整行程。

    先建 origin -> destination 映射,再找唯一的起點,
    然後順著映射逐站接出整條路線。
    """
    next_stop: Dict[str, str] = {}
    destinations: Set[str] = set()

    for origin, dest in tickets:
        next_stop[origin] = dest
        destinations.add(dest)

    # 找起點:不作為任何票終點出現的那個起點
    start = None
    for origin, _ in tickets:
        if origin not in destinations:
            start = origin
            break

    # 順著映射逐站接出行程
    itinerary = [start]
    current = start
    while current in next_stop:
        current = next_stop[current]
        itinerary.append(current)

    return itinerary

時間複雜度:O(n),n 為車票數量(建映射與走訪各一遍)。 空間複雜度:O(n),用於映射和終點集合。

Follow-up:邊界情況

面試官追問了幾類異常輸入:

我的處理是先和面試官澄清:輸入是否保證是一條唯一合法的鏈? 確認口徑後再決定——如果不保證,就要偵測異常(比如起點數 != 1、接出的站數 != 票數 + 1),並按約定回傳錯誤、回傳空,或報告具體異常,而不是默默給出一條錯的路線。


備考建議


FAQ

Q1:Pinterest VO 一共幾輪,都考什麼?

我這次是四輪:一輪 BQ 行為面、兩輪 Coding、一輪系統設計(含兩道設計題)。BQ 看 tradeoff 表達,Coding 偏分步演進,系統設計看你會不會先確認需求。

Q2:Coding 題為什麼喜歡「分三步」這種形式?

它想看你能不能在需求逐步增加時保持結構不崩。棋子那題從 Queen 到加障礙物再到多種棋子,好的寫法是共用一條方向掃描路徑,加需求只改方向表和加一個 break。

Q3:廣告事件聚合看板,為什麼用 Flink 而不是批次處理?

因為看板需要近即時的多維聚合,事件是持續流入的。Flink 能做串流 join 和視窗聚合,配合 Kafka 承接事件、Redis 存短期映射、OLAP 存聚合結果,整條鏈路是即時的。

Q4:目錄批次上傳為什麼不讓客戶端切批上傳?

客戶端切批會把重試、進度管理、失敗恢復都壓到客戶端,可靠性差。更好的做法是上傳大檔案到物件儲存拿 job ID,伺服端用訊息佇列非同步解析、校驗、分片、批次寫,客戶端只輪詢進度和失敗詳情。

Q5:行程重建遇到重複起點或環怎麼辦?

先和面試官確認輸入是否保證唯一合法。如果不保證,就要偵測異常:起點數不為 1、接出的站數與票數對不上等,按約定回傳錯誤、回傳空或報告異常,不要默默輸出錯誤路線。


正在準備 Pinterest 的 VO? 我們熟悉 Pinterest 四輪的節奏——BQ 的資料化 tradeoff、Coding 的分步演進、系統設計的需求澄清,能提供從模擬到即時的 VO 輔助與 VO 代面,陪你把每一輪打磨到位。

立即添加微信 Coding0201取得一對一客製備考方案

聯絡方式