想提升演算法面試的實力嗎?來參加程式競賽吧!

Arthur Lin
17 min readJun 1, 2023

--

2022 年底開始科技業腥風血雨,但也很多人才剛踏入軟體工程師的旅程,開始想刷 Leetcode 準備挑戰頂尖大公司的面試,但其實準備到一定程度後,有些人會開始感到缺乏動力,如果想更進一步的話,基於自身經驗,我覺得參與競賽是滿好的方法之一,不管是較偏面試導向的 Leetcode weekly contest 或其他更競賽導向的平台(如 codeforce)都行,除了緊張刺激的氛圍可以大幅訓練面試者的心理素質,競賽的排名也是一種量化證明自己進步軌跡的方式之一,讓你對自己的實力有更客觀的標準。更甚者,當達到某個程度以上,或許你還有機會跟從事各種智力或體育賽事的人一樣,藉由挑戰自身的極限進入美好的心流體驗。

但雖然面試與競賽的考題內容看似雷同,本質上還是有明顯的差異,如果最終仍是以面試為目標,那我們該特別注意哪些地方呢?這篇就是想好好聊聊這部分,以免你競賽成績越來越好,名次越爬越高,結果一上場面試表現卻完全不如預期,就很可惜了。

先前情提要一下,如果對於基本知識面的準備方向還不清楚,可以看我之前的系列文章。

而關於大公司面試也可以看這篇,裡面其實也有淺談到競賽的部分。

但這篇我們特別針對程式競賽(Competitive Programming) 與技術面試(特指 Coding Interview,且是真的要白板或編輯器寫 code ,有人跟你互動的那種),再好好比較一次異同之處。並在文章最後,延伸聊聊不管競賽還是面試,跟真正軟體工作上技能重合程度又有多少。要知道關鍵差異在哪,就先從競賽與面試各自的重點開始剖析吧!

技術面試(Coding Interview)核心重點

對一個演算法與資料結構的問題,要能做到以下幾點

  1. 理解問題:能否正確理解問題,並與面試官確認
  2. 觀察力:是否能發現題目漏給的重要條件,並事先確定清楚
  3. 溝通能力:能否良好的表達想法跟邏輯,與對方合作
  4. 細心:自己想測試時,能不能清楚的把各種特殊條件也都考量進去
  5. 實作能力:能不能做的又快又好,沒有 bug,且程式碼的品質也顧慮的到

可以看到,總的來說由於考題的形式是資料結構與演算法,所以核心重點仍是你要具備足夠的知識,並且能順利實作出來,但面試官想看的面向卻遠遠不止於此,而是很廣泛的一般性工作能力。接著我們來看看程式競賽。

程式競賽(Competitive Coding) 核心重點

一輪比賽通常有數量不定的幾個題目(3~5 題甚至 10 多題都有),並有一個限制時間,一般來說成績與排名依據是完成的題目數量、題目難度的配分、完成時間先後、是否有錯誤罰時、有些比賽還有 hack 機制(這比較複雜不懂也沒關係,這邊不多做解釋,有興趣的人可看這邊)。

因此重點就是要做到:

  1. 想辦法在限制時間內,完成儘可能多的題目,並儘可能最快速完成
  2. 減少送出的錯誤次數,避免被處罰扣時間

至於題目通過的標準,一般只要實作的時間與空間複雜度正確即可順利通過,並且在大多數時候,考慮時間複雜度的重要性遠遠遠大於空間複雜度。

看完兩者之後,我們就可以來比較異同之處了。

兩者相同的點:

最核心是相同的

  1. 具備足夠的資料結構與演算法的基礎知識。
  2. 快速理解題目並找到正確的解法,這考驗的都是觀察力與熟練度。
  3. 最後是盡可能不要犯錯,一樣在考驗細心程度,寫 code 時穩定邏輯不出錯,並能考量到各種奇怪的輸入都不會造成程式有 bug。

兩者相異的點:

但也有很多不同的面向

  1. 最關鍵的差異是,競賽是零溝通的(少數些團體賽可以跟隊友溝通,但這跟和考官溝通還是完全不一樣的),你通常可以一句話不講的專心投入思考,但面試卻非常非常強調溝通,在沒練過的情況下,一直要你講想法可能會打斷你的思緒,因此太習慣競賽的話這點反而會很不適應。
  2. 競賽時對相同問題,你只需要熟練一種解法即可,但面試可能會希望你能分析不同解法之間的優缺點,甚至在你做出來後,要求你換一個效能一樣好但思路不同的做法。因此競賽久了,有時候會太習慣經典演算法眾多最佳套路中的其中一個,以保證又快又穩,結果導致其他幾個早就忘得一乾二淨了。例如基本的三種 Tree DFS Traversal 如果要你不用遞迴寫,你有辦法應付嗎?當然啦,頂尖高手隨手都能依據基礎邏輯瞬間推衍出不同版本的正確做法,但大多人都沒到這個程度,還是得靠熟練。
  3. 競賽不管你的 code 寫的多醜多亂,只要跑起來能過就好,為了省時變數名稱時常 a, b, c, x, y 亂取(工程師都知道想命名真的很辛苦),且全域變數亂開也沒差,甚至也不介意你複製貼上已經準備好或臨場 Google 到的範本(例如 Dijkstra 的標準寫法)。但這在面試都是不允許的,你要能從頭開始寫 code,一開始就要儘量寫的像是個成熟的工程師,在 naming 跟 function 切割上有想法,更進一步你有先研究過該公司喜歡的 coding style 也是加分,至於查詢東西則是盡量避免或最好事先徵求同意。
  4. 語言上的限制,新手階段的競賽,或 Leetcode Contest 比較沒有語言上的差異,主流語言基本上都支援,這點跟面試比較像,不會強迫使用特定語言。但很多更專業的競賽平台,由於時間限制卡的比較緊,大多語言有可能因為跑太慢被卡掉,所以幾乎只剩 c++ 跟 JAVA 能用了,甚至很多 c++ 選手還會捨棄 std library 的資料結構,改用原生的陣列如 int arr[] 來極限加速,幾乎可以說只用到 c 語言的功能。
  5. 競賽不需要考量長期的工程情境,但面試卻可能會希望你考慮進去,例如某個做法,可能造成一個系統長期使用後,會不斷累積 data 在 memory 而無法刪除,就是個明顯的缺陷,由於競賽的題目只會被機器跑一輪有過就好,所以不需要考慮這種情況。
  6. 考題的侷限性也有差異,雖然競賽考題豐富度遠遠大於面試,但由於機器條件的限制,某些時間與空間複雜的重要概念是競賽無法考出來的,所以你在比賽中幾乎永遠遇不到也練習不到,這類題目雖然佔比不高卻又不可忽視,但這部分比較複雜,有興趣知道的我放在文章最後附錄解釋,可以晚點再看。
  7. 最後一個是我覺得出題者心態上會有差異,競賽的出題者往往會希望題目可以更加聰明有趣一點,不喜歡死板板的被人一看就知道你想考什麼知識點,即使是簡單題目也會想儘可能繞點彎藏一下,但面試官則相對可以接受無趣的題目,因為重點只是要審核你有沒有紮實的具備他想要的知識。這點在新手階段差異不顯著,但後期感受會越來越深。

知道程式寫錯的時機點

這個部分稍微特殊所以單獨提出來講

  1. 送出當下就告訴你錯誤 case:有的競賽比較佛心,你提交的答案錯誤,他會給你一個 testcase,告訴你這個 case 他跑不過,這其實是個巨大的提示,會很大程度幫助你找到問題。
  2. 送出會馬上知道錯誤,但不知道錯哪個 case:大多數屬於這種,而也有一半一半的,就一部分會告訴你,但也有部分隱藏測資賽中你看不到,Leetcode Contest 屬於這種,且大多數看的到,
  3. 送出後完全不知道對錯:只有賽後才會知道,所以也沒有第二次機會做修正了,Facebook Hacker Cup 屬於這種、Google Code Jam 則是部分會當下知道對錯,部分隱藏(2023 年以後停辦了 QQ)。

那回到面試的角度來看,我會建議自我練習時,把要求放到最高,也就是要求自己要在完全不知道回饋的狀況下,一次提交就做到正確(也就是假設你賽後才會知道正確答案的概念,如果錯了就是 0 分)。這時候你需要很強大的自己想 testcase 的能力,還有很縝密的邏輯思維與完善的考量特殊案例。

但真正上場面試的話,則是考驗你的溝通技巧了,在你跟面試官一來一回討論的過程中,有時候是可以得到提示的,並沒有像上述練習時一樣嚴格。當然,每得到一次很明確的提示也可能意味著被默默扣分(這點每間公司,每個面試官想法都不一樣),但扣分不代表直接 fail 零分,如果整場大多數時候都順利,還是可以拿到好評價的。

簡單總結一下重點就是,競賽的題目通常更有創意,更考驗觀察力,且必須獨立完成,而面試則是一個互動的過程,且每個面試官有他不同的看重的細節,這些細節在競賽中往往用不到,要小心準備。

說了這麼多,現在來看個圖應該更清楚,我們可以分成不同階段來看。

初級階段

初階程式競賽題目(約是 Leetcode Weekly Contest 前 1, 2 題難度),與 Junior 面試需要的知識,可以說幾乎是重合的,因為基礎知識都是那些不會跑掉,而 Junior 面試也不會太過要求精準的表達能力,一般光是能正確的做出來,不要太難以溝通,就能留下很好的印象了。

中級階段

兩個圈圈會更大一點,可能面試一些較知名的新創公司或老牌大公司(但還不到 FAANG 或類似的一線知名公司)會需要,然後競賽要會的東西稍微多了些,程度約是 Leetcode Weekly Contest 前三題(第 4 題較簡單時也可以),或類似 AtCoder Beginner Contest、Codeforce div 3, 4 一類專業競賽平台提供的新手區比賽(但即便標榜新手區,中間以後的題仍有可能遠超出這階段所需程度)。可以說面試要會的知識,競賽也幾乎是全都要會,而且競賽需要的知識會再超出一些,同時也會因為上面提到的出題者希望更聰明有趣,而出現一些很吃觀察力的特殊題目,但大體來說觀念仍然是相通的,可以當成對面試準備的加強版訓練無妨。而面試則是會額外更看重溝通,但仍然是正常範疇,知識量足夠完整,並正確的表達出來就會有好印象。

高手階段

到高手的階段,面試以 FAANG 或同等級公司所需當作標準,競賽則以Codeforce div 1, 2 拿好成績或國際大賽打到準決賽以上為標準的話。程式競賽需要的知識量,會開始遠大於面試,若想要到頂尖比賽的 final 輪或 Codeforce top 50,難度則已經遠遠甩開面試好幾條街了。這邊綠圈雖然看似變小,但其實要想成是比上面中級階段再略大一點點,是因為紅圈膨脹太多了,導致綠圈看起來相對小。

一般這個程度的競賽者也不打 Leetcode Contest 了(Hard 對他們來說都跟吃飯喝水一樣輕鬆且無聊),而是會去如 Codeforce 等更專業的競程平台,或 Google Code Jam 或 Facebook Hacker Cup 等知名國際大賽。而反過來,面試這邊則是要注意很多知識以外的面向,可以說知識僅是眾多需求的環節之一而已了,其他面向的準備也同等重要,甚至更重要。

所以有時候競賽高手面試未必就會順利,但相對來說競賽高手要針對面試刻意準備,要花的力氣與難度,還是比一般人想在高等級競賽打出好成績,相對簡單的多的。也因此會有許多知名一線公司的員工都是從小一路打競賽出身的現象。

最後,可能大家會好奇的,學這些到底有什麼用?我們把工作真的會用到的部分也放進來簡單看一下,可能會像這樣。

菜鳥階段

在圖上用藍圈表示,這可能會依據領域而有點不同,但以我自己比較熟的 Web & Mobile 領域來說,Junior 工程師確實很諷刺的就是,真正用到的資料結構與演算法知識確實極少,遠遠小於面試可能會被考的知識量,也是時常引發爭論的地方,讓很多人質疑究竟為何要考這些。(有些團隊可能需要的會更多,那就會更偏向下一張圖)

但無論如何我覺得這些知識知道總是好的,下面可以看到為什麼。

資深階段

中階相對差不多就跳過,但如果到了資深的 Senior 階段,你要成為一個真正的資深工程師、Team lead、Tech lead、Tech Manager 甚至 CTO,你得在眾多技術中快速的做評估與選擇,或是理解被抽象封裝過的介面底下的世界,讓你能做一些很底層的調試,例如調整 DB Config 進而影響優化器的決定、或對 Distributed System 的 partition 機制做某些複雜的排序或決定分割方式,甚至還有可能你自己也成為了這些底層工具的開發者,例如你就是那個開發新的資料庫、或做搜尋引擎核心的人。

到這個階段,你的問題通常網路上也找不到答案了,甚至一點方向都很難找到,因為你的問題大概就你的公司或少數幾間公司才有的情境,別人也未必有分享出來,即使分享了,你的基礎知識不夠也未必看得懂。以筆者自身例子來說,進入 Google 工作後發現他們有一整套內部推薦的開發工具,如後端平台或資料庫都是我以前從來沒用過的,一瞬間要閱讀海量的相關文件解釋這些工具的特性與背後的實作方式,如果不是靠著對這些 CS 知識有好的基礎,就很難快速掌握這些工具的最佳使用方式(Best Practice),可能會做出看似正確但其實暗藏禍根的不良系統設計。

到這時候,這些資料結構與演算法的知識,當然還包括其他重要 CS 基礎知識,才會突然變成你的最佳利器之一,是思考與判斷最重要的導航指引,還有跟人討論的重要語言。圖下的兩個藍圈分別代表工作上需要的知識還有額外的能力。

關於圈圈中不重合的點,首先我認爲相較於競賽,面試考的東西還是相對跟實務較貼近的,高等級的競賽可以完全看作另一種特殊能力了。

至於面試與實務的差異,由於面試仍然受到時間限制,有些實用性我覺得很高,但由於太複雜,以至於很難設計成面試考題。例如經典紅黑樹、關聯式資料庫的 B+Tree、模糊搜尋用的複雜字典樹、地圖用的 R-Tree、編譯用到的 Syntax tree(實用的樹真的很多),我覺得都很值得認識一下,但面試如果要你 40min 刻出這種東西就有點過分,僅聊聊的話又不夠有量化的參考性。而有些公司面試會蹦出來 Brain Teaser 類的題目(例如經典的 N 層樓兩顆雞蛋),除了可能可以窺探面試者是否聰明(但這點我強烈存疑)以外,我一時還真的想不到任何的實用價值,有讀者發現的話歡迎告訴我。

至於額外的能力方面,主要差異在於,工作確實需要好的邏輯推理能力、寫 code 能力與溝通能力沒錯,但通常不需要全部一起來,也就是你往往不會需要在推理設計與寫 code 的同時還不斷的與人對話,而是會分開處理,例如一開始先溝通需求(對話),然後做設計(安靜),再討論設計(對話),然後實作寫 code(安靜),最後驗收成品是否運作正常且正確(對話)。很可惜,面試受限於時間,因此受試者得刻意練習一下,如何在設計與寫 code 時也要能不斷跟人解釋與討論你的想法,對許多工程師來說,沒練過幾乎完全做不到,即使他的工作能力很優秀。

知道這些,是希望讓大家可以在準備面試的時候,更知道哪些是真正重要的,對你的工作長遠來說有幫助的,哪些則是為了面試不得不去唸的。然後當你發現其實多數東西都還是有用的,就不會有一種僅僅為了考試而唸書的苦悶感,覺得在浪費自己的人生而排斥,進而錯失了真正寶貴的東西。

如果你年紀還小(大學生或以下),對程式有興趣,其實我是滿推薦可以積極的參與程式競賽相關訓練,趁着無後顧之憂時,單純的挑戰看看自己的極限在哪里,除了有趣與自我證明外,也是一種訓練提升自己思考與抗壓力的好方法,不用太在意實際工作會不會用到,訓練的過程本身就會帶來巨大的收穫,未來面試用的上只算是附帶的 bonus。

但如果你是非本科系轉職進入 CS 領域的成人,可能需要時間更對焦在面試準備或工作的實用性上,那可以參照本文,並依據自己的階段,去判斷競賽對你的幫助有多大,還有該注意那些地方,找到最符合你的準備方式。

對我自己來說,競賽相比於純粹的面試準備仍然是有趣的多的,會有一種熱血跟挑戰的爽快感,就跟打遊戲不斷進步破關一樣,所以雖然我也是年紀很大才開始學演算法,但掌握基礎知識後,中期我都是用固定參加競賽的方式(主要是 Leetcode contest)在練習的,直到發現那個紅色圈圈開始越偏越遠,好像有點快失控了,才把自己拉回頭去補面試需要的環節。

祝大家順利!

附錄:關於那些競賽考不到的重要觀念

  1. 競賽中,單純 log(N) 的時間複雜度很難出題

因為現代電腦執行太快,一個 O(N) 的程式,可能足以讓 1e9 數量級的測試資料都在 1~2 秒內跑完,如果我要設計一個題目,讓你一定要 O(log(N)) 才跑的過,那我的測資要大到很誇張,而這要浪費的空間太大了,甚至讀檔時間都太長,所以你可以想像例如最單純的 Binary Search 題目(Array 中找 Target) 是無法在競賽出現的,但這卻是基礎知識最重要的一環。

當然,聰明的出題者還是想得到很多方法來考這東西,例如要求你在 0 ~ 1e18 的整數空間中搜索某些條件,而不用真的給你整個 Array ,或是轉幾個彎來考 Binary Search,有興趣可以看我之前寫的這篇,是競賽基礎必會的題型,即使對面試來說認識一下也是好事 ,但這已經超過基礎新手該會的。

2. 競賽時,有時候超出個 log(N) 的時間複雜度還是能通過

例如最佳做法是 O(N),但你找到一個很簡單的 O(NlogN) 的做法,有時運氣好也可以擠過去。這是一樣的道理,多一個 log(N) ,在 1e9 數量級下最多就慢 30 倍,由於出題者往往也不想抓到太緊,導致有人寫了個正確 O(N) 的做法,但寫法稍微混亂囉唆了些就被卡掉,但反過來說,在大測資不夠多的狀況下,就是讓一些人在多一個 log(N) 的情況下,極限優化後有機會偷過去成功拿分(尤其是靠 c++ 之類速度較快的語言)。或是 Leetcode 競賽中的前兩題,往往測資給的很小,甚至你寫了個 O(N²) 都能塞過去拿分。但面試中往往會要求最佳解,這樣你就還是得乖乖找到 O(N) 的解法,難度會上升很多。畢竟現實世界,如果兩個做法效能差了 30 倍,絕對很難說這只是個小誤差而已,大部分時候關鍵處優化個 50% 就是很重要的優化了。

同樣道理,面試官如果很追求的話,有時最後還會要你把 O(2N) 的寫法繼續優化到 O(N),通常發生在 two pass(兩個非巢狀 loop)變成 one pass(僅用一個 loop)的狀況,最經典的一題大概就是 Leetcode 的 Sort Color。從時間複雜度的角度來說雖然一模一樣,競賽也幾乎不可能設計題目去要求這種優化,但面試最終要考核的是實際工程能力,速度能快兩倍,一樣在現實系統上很有意義。

3. 很難考空間複雜度的最佳化

同樣的問題,機器上要限制空間有點麻煩,你往往要讓測試資料超級大,才會跑一跑出現 MLE (Memory Limit Exceeded),這樣對評分系統負擔也很重。例如 Dynamic Programming 有些很常見的優化方式(e.g. 背包問題),可以降低空間複雜度的維度,從 O(N²) 變 O(N),但比賽中往往不做也可以通過,練習用的 Leetcode 題目也一樣會讓你 AC。

但面試往往希望你能做到最優化,因為他沒有機器運算時間與空間的限制,可以很單純的考你理論上的最佳解。所以我建議準備頂尖公司面試的人,練習時一定要特別在空間複雜度上多留意一點,因為這塊非常容易被忽略。

至於這件事實際工作上是不是那麼重要,就要看產品而定了,如果你是硬碟空間遠遠大於實際需求的系統,例如流量沒有到很吃緊的 Web 後端,且多買個幾十 TB 硬碟,幾百 GB 記憶體,對公司開銷來說不痛不癢,那大可不太在意,但如果在一些需要對記憶體斤斤計較的地方,例如 embedded system,或想要相容資源較不足的舊款手機的 mobile app,或不知道要跑在哪個 IOT 小裝置上的系統,那就有可能成為決定性的影響。

Sign up to discover human stories that deepen your understanding of the world.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Arthur Lin
Arthur Lin

Written by Arthur Lin

軟體工程師/後端技術導師。對於程式和教育充滿熱情,看到學生的成長會很開心。將複雜的知識整理成清晰易懂的樣貌並分享出來,不僅能幫助別人,也是我自己最喜歡的成長方式。

No responses yet

Write a response