Google Virtual Onsite 面試全解析 | Intern R1 技術題與經驗分享

剛結束 Google Virtual Onsite 第一輪技術面(R1),整體體驗非常典型的 Google 風格:題目不偏、不怪,但會透過 follow-up 深挖你對資料結構、複雜度和工程場景的理解,而不是隻看你會不會寫程式碼。面試官節奏不算快,一共兩道題,每道都有明確的 follow-up。

第一題:二叉樹遍歷與併發設計

題目本身是一個經典但很容易被低估的設計題:
設計一個迭代器,對二叉樹進行非遞迴的中序遍歷。每次呼叫 next() 返回下一個節點值,要求平均時間複雜度 O(1)。

我一開始先和麵試官確認了介面語義,比如是否需要 hasNext()、是否允許預處理整棵樹。明確不允許一次性展開後,就直接進入標準解法。核心思路是用棧來模擬遞迴呼叫過程。初始化時,從根節點開始,把當前節點以及所有左子節點一路壓棧,保證棧頂始終是當前中序遍歷中最靠前的節點。

每次呼叫 next():
彈出棧頂節點,這個節點就是當前的返回值;
如果該節點存在右子樹,就從右子節點開始,把它以及它的所有左子節點依次壓入棧中。

這樣做的好處是完全“按需遍歷”,沒有提前生成完整的中序序列。單次 next() 雖然在最壞情況下可能會壓入多個節點,但從整體來看,每個節點只會進棧、出棧一次,因此平均時間複雜度是 O(1),空間複雜度是 O(h),h 為樹的高度。

面試官比較滿意這個部分,隨後進入了真正的加分點。

Follow-up:多執行緒環境下的執行緒安全設計

面試官提出了一個很 Google 的 follow-up:
如果在多執行緒環境下,多個迭代器可能同時遍歷同一棵樹,如何保證執行緒安全?

這裡的關鍵不是“加鎖就完事”,而是要區分共享狀態和私有狀態。

我的回答思路是:
如果每個 iterator 都維護自己獨立的棧結構,而樹本身在遍歷過程中是隻讀的,那麼多個迭代器併發遍歷同一棵樹本身就是執行緒安全的,不需要對樹加鎖。

真正需要考慮同步的,是單個 iterator 是否會被多個執行緒同時呼叫 next()。
如果存在這種情況,可以在 iterator 內部對 next() 方法加互斥鎖,或者使用執行緒安全的資料結構來保護棧的讀寫。

如果進一步追求效能,也可以透過“每執行緒一個 iterator”的設計來規避鎖競爭,這樣從設計層面消除併發寫問題。

面試官明顯是在考察你對併發邊界的判斷,而不是死板地回答 synchronized。

第二題:子陣列和等於 k 的計數問題

第二題是一個非常經典,但 Google 依然會考的基礎題:
給定一個整數陣列,計算有多少個子陣列的和等於目標值 k。

我直接從暴力解法切入,說明 O(n²) 不可行,然後引出字首和 + 雜湊表的最佳化方案。具體做法是:
遍歷陣列的同時維護當前字首和 sum。
用一個雜湊表記錄“某個字首和出現的次數”。當遍歷到當前位置時,如果 sum – k 在雜湊表中出現過,說明存在若干個以當前位置結尾、和為 k 的子陣列,數量正好等於 (sum – k) 出現的次數。初始化時要在雜湊表中放入 {0: 1},代表“字首和為 0 出現過一次”,用來處理從下標 0 開始的子陣列。整體時間複雜度是 O(n),空間複雜度 O(n)。

Follow-up:陣列包含負數時如何最佳化?

面試官在 follow-up 中強調了一點:陣列中可能包含負數,這會不會影響演算法?

這裡其實是一個“陷阱式確認”。我的回答是:
負數的存在確實會讓字首和不再單調,因此無法使用雙指標或滑窗那一套,但雜湊表記錄字首和的方法本身並不依賴單調性,時間複雜度仍然是 O(n)。如果從工程角度考慮,可以透過預估字首和的取值範圍(最大值、最小值)來最佳化雜湊表的容量分配,減少 rehash 成本,但演算法級別不需要改變。

面試關鍵節點的實戰助力

在 OA 或 VO 階段遇到瓶頸,其實不必慌。大廠的題型雖然看起來多樣,但往往可以分塊拆解:演算法核心、複雜度分析、邊界條件、思路表達。我們陪同一些同學完整走過許多大廠的 OA + VO,全程 實時輔助 ,面試過程中節奏更順,答題也更有條理,透過的機率自然更高。

author avatar
Jory Wang Amazon資深軟體開發工程師
Amazon 資深工程師,專注 基礎設施核心系統研發,在系統可擴充套件性、可靠性及成本最佳化方面具備豐富實戰經驗。 目前聚焦 FAANG SDE 面試輔導,一年內助力 30+ 位候選人成功斬獲 L5 / L6 Offer。
END
 0