LeetCode 解題的思考策略,刷題的4個階段

什麼是 LeetCode?

LeetCode 是一個程式語言的線上題庫平台,收集了大量科技、軟體公司面試時所出現的技術題(技術面試中俗稱的白板題)。LeetCode 會被很多資訊產業的求職者視為「準備面試時」的題庫,而對題庫進行大量練習的行為稱為「刷題」。

▶ 官方網站:https://leetcode.com/

LeetCode 介面

 

LeetCode 是一個線上解題系統

OJ(Online Judge)全名是線上解題系統,早期是用於演算法競賽(競技程式)的線上系統,即時評測參賽者所提交的。在眾多的線上解題系統中,LeetCode 以「公司面試題目」為導向殺出一條血路,提供技術面試中常見的題目,作為平常的練習或是面試前的準備。當然除了 LeetCode 之外,也有其他適用於平常練習的平台都有各自的特色:

  • Codility:更強調面試體驗,提供題目給公司作為面試使用
  • HackerRank:適合初學者,內容包含基礎題目到進階難題
  • Codewars:導入許多遊戲化與社群的機制,讓刷題更有趣更好玩

這些解題平台的目的其實都是提供一種「評斷程式能力到什麼階段?」的目標,不管是幫助開發者確認自己的能力到什麼階段或是提供面試方作為篩選的標準。

Leetcode是什麼?誰需要刷題?工程師面試要刷到什麼程度

點我免費領取非本科轉職工程師指南!

為什麼要刷題?

那為什麼程式開發者需要刷題呢?程式學習其實跟數學學法很像,除了理論公式之外,也需要搭配題目實作。一般而言,我們可以將程式的學習分成兩個階段:

  • 程式語法
  • 解題能力

「程式語法」指的就是你能不能看得懂程式碼,理解範例是如何運作的。但「解題能力」是指當你遇到一個問題時,能否轉換成程式的角度回答問題。很多人會問資料結構或演算法對工程師寫程式來說重要嗎?尤其是那些非本科系的轉職者,更會擔心資料結構或演算法是否會成為求職時的門檻。

資料結構或演算法其實就是程式碼經年累月淬煉出的精華,經過整理而成的武功秘笈,適合已經會寫程式但想要把持續鍛鍊程式能力的人。但如果連程式的基本邏輯都尚未熟悉者,就貿然的進入刷題的世界中,也很容易原地打轉。總之,我自己覺得寫出會動程式不難,但要把程式寫好非常難。追逐程式實作技巧也許短期內會覺得很刻意,但立志寫出一個精簡高效的程式才是身為一個工程師該有的浪漫。

演算法對一個工程師的意義?如何提升實力?

寫程式不只是學習程式語法,更需要搭配實作練習

那究竟該如何持續鍛鍊程式的技巧,找資料結構或演算法的理論課或是書來讀就好了嗎?

理論課其實也是從各種經驗跟問題所歸納出來的方法,所以理論課其實就是在教你各種手法跟操作(很多時候很抽象),就像你學習的數學時候會背公式那樣。但更重要的其實是觀念是如何建構,以及應該如何活用於題目當中,這才是解題/刷題過程中逐漸養成的。刷題的重點來源培養解題的手感,如何把從問題中使用正確的手法。刷幾題才夠其實是一個假議題,當然是刷越多越好啊(但現實是沒有時間)。所以我會建議不要盲目地刷題,應該思考如何有系統的解題,從一個題目當中盡可能嘗試練習到不同的思考角度,培養思考問題的廣度。

以最接近業界實踐的課程,幫你有效完成全端網頁開發的技能與求職準備

什麼時間點該考慮 LeetCode 刷題?

▶「你有聽過「白板題」嗎?你知道技術考試在面試時會用什麼形式出現嗎?」

How to: Work at Google — Example Coding/Engineering Interview

這張圖是來自 How to: Work at Google — Example Coding/Engineering Interview ,由 Google 分享的的 Mock Interview 過程。技術面試這是許多人在面試前會有的焦慮,而透過刷題是用另一種「技術測驗」方式,讓我們習慣從題目來理解技術的過程。一般來說,技術面試題有兩種考法:「線上/紙本技術面試測試(前測)」或「白板題(現場)」。線上/紙本技術面試測試著重的是「結果」,單純就看最後的分數過不過作為最低的篩選門檻;而現場白版題著重的是「過程」,除了解法之外還有當下的思考脈絡與持續優化的過程。

但是思考脈絡與持續優化都不是可以短期惡補的,需要的是長期的訓練與培養。因此,我不建議只把 LeetCode 當成是面試前的速成班,應該要視為長期持續練習的一種途徑。所以如果在寫程式的過程中有以下兩種狀況,那就很值得思考是否該適合踏入刷題的世界:

  • ① 準備面試的階段
  • ② 想寫出更好的程式

而在刷題的過程中訓練的是「對於解題手法的想像力」,解法就是像是你的武器,當你面對一個問題的時候你可以拿出幾種武器取決於你的經驗與積累。所以對初學者而言首先要求的是寫出可以動的程式,可是當遇到「效能」或是「更複雜的問題」時,這些技巧就成為你無形之中的優勢。總而言之,我覺得刷題的重點是要找到一個適合自己的節奏,刷幾題不是重點,重點是你從每一題中掌握了多少學到了多少而且持續的變強。

演算法面試準備與 LeetCode 刷題重點心法 – 新手工程師職涯升級密技

幾種解題的策略

LeetCode 題目中從早期的 200 多題,到現在已經有 1000+ 題以上,刷題的難度也大大提升。對於剛開始嘗試要開始刷題的時候通常未遇到以下盲點:

  • 不知道 從哪一個題目開始?
  • 不知道 要解多少題才夠?
  • 不知道 是否真的理解?

而這些問題的背後,都是來自於不知道該如何有效刷題的焦慮感。

如何高效刷題,正確的解題順序

早期坊間都會流傳一些「只要刷滿多少題,就能夠通過某某公司技術面試關」的江湖傳言,不過隨著題目量與日俱增,透過「刷好刷滿」的解題策略早已難以落實。因此,在 LeetCode 題目越來越像題海的同時,單純地大量刷題策略反而會造成反效果。以下整理幾種在不同情境時適合的刷題策略:

① 時間有限的話,從「熱門題」開始刷

熱門題指的是的是那些「面試」時常出現、面試官愛考的題目,在 LeetCode 中也有針對不同公司推出熱門題組組合。所以如果你時間有限且方向明確的情況下,這是一種短期的策略。

② 搭配理論課程,從「分類題」個別刷

如果你還是學生或是正在讀程式設計、資料結構、演算法之類的理論課程,那麼你可以搭配 LeetCode 中的題目分類,針對目前進行的章節選擇相對應的題目進行刷題。理論課程教你的語法及歸納後的方法,更需要搭配實作練習才能學得好。

③ 長期持續鍛鍊,從「難易度」逐步刷

最後一種是將 LeetCode 當成是一種長期持續練習題庫的話,那我建議打散熱門與分類這兩個的維度,從無差別的方式進行刷題,把重點放在「解題」的過程中。雖然說無差別,但還是需要有個順序進行,不然一開始遇到難題就卡關怎麼辦。因此,我們會議可以使用 LeetCode 提供的「難易度」與「通過率」兩個指標排序,從簡單且很多人都通過的題目慢慢往比較難的題目開始逐步進行。

刷題的四個階段

除了「解題順序」之外,如何最大化一個題目的效益也是刷題過程中重要的關鍵。比起一題解完就換下一題這樣的方式,我們更建議花多一點在一個題目中,盡可能地持續迭代、持續優化並且思考沈澱,讓你從一個題目掌握到更深更廣的效益。就如同我們前面所講的,刷幾題不是重點,重點是你從每一題中掌握了多少學到了多少而且持續的變強。

動手之前先思考 → 初探直覺解 → 刻意優化 → 舉一反三

接著當開始進入「一個題目」的解題過程時,我會建議可以分成幾個階段:

動手之前先思考

快速讀完題目知道,先確保題目的 Input、Output 與要求分別是什麼之後。請先不要就貿然的開始寫程式碼,應該是先思考一下大概的解題步驟以多想幾種不同的策略,這個時候可以搭配虛擬碼(pseudocode)或程式流程圖輔佐。

初探直覺解

第二步就可以嘗試把剛剛想到的策略付諸實現,我會建議可以先從「直覺解」、「窮舉法」甚至是「暴力法」開始著手,在不要考慮效能的情況下先求可以邏輯正確可以跑出答案即可。

刻意優化

產生完第一個解法之後,接著就可以從直覺解當中進行重構(Refactoring),嘗試從原本的解法中挑出可以優化的地方來改進。一般來說,程式的優化可以分為「更精簡的程式碼」或「更快地執行速度」兩個角度下去著手。但一個題目能不能馬上想到可以優化的空間,在剛開始是需要「刻意」練習的,有時候可能比想像中更困難。因為腦袋的思考會有先入為主的觀念,有時候不容易跳出既有的框架。所以我會建議這段可以不用急,當寫完一個題目之後可以三個小時、三天,甚是是三週之後再回來看同一個題目,把它當成新的題目重新解解看,反而更容易寫出不一樣的解法。

以我的經驗來說,一個題目至少可以嘗試到三到五種的解法。這樣的效益比起單純的解之外,更多了一層「迭代優化」的培養。

舉一反三

最後當你從一個題目離開後,可以試著解類似題,並且從這個題目的解法「遷移」到另一個類似題中。在 LeetCode 有很多類似的題目,或是一個題目的進階版。從這樣的練習你,你會默默地發現這些看似類似或進階的題目背後都有一些有趣的轉化,而這個轉化就是在培養能不能「迭代優化」的過程。

刷題過程的心態與技巧

其實寫程式本身就是一種對話,你不是機器人、也不是百科全書,所以很難一昧、單向的進行解題。回想我們平常在開發程式的過程,其實上網搜尋、查閱書籍的時間一定佔用不少的時間,真正「敲程式碼」的時間反而有限。因此,不管你是在面試當下面對面試官的解題或平常在練習刷題時,透過「對話」釐清需求、發想解法都是蠻建議的方法。

我自己會把刷題過程的心態與技巧的大概可以總結為這三項:

  • 勇敢的提出互動與溝通(不管是自言自語或是與面試官交流)
  • 嘗試用自己的話解釋題目化,將抽象的情境轉化為具體的問題
  • 動手之前先思考,將過程拆解成 step-by-step 的解題步驟

就像經典的 黃色小鴨除錯法(Rubber Duck Debugging) 一樣,你需要其實是那個「思考」的過程。

如何寫出「更好的」的程式碼?

所謂的「如何理解題目背後的設計思維」這個問題,需要先思考「寫程式的本質」到底是什麼?而 LeetCode 的題目就是其實就是一種「評斷程式能力到什麼程度?」的面試題目匯總,比起刷題更值得關注的是該如何從這些練習中鍛鍊出更好的程式碼品質開發功力。

所謂的寫程式就是利用電腦的記憶與運算,根據 Input 產生 Output 的過程,而演算法指的是在有限步驟與時間內執行的程式,這也是寫程式與演算法之間最大的差異。演算法我們更執著「多少個步驟」或「多少時間」可以完成,這個效能與複雜度是否堪用。

JavaScript 全端開發課程,18 週進度班帶你半年轉職工程師

從程式設計到資料結構與演算法

程式設計利用「變數」使用記憶體儲存與記憶、利用「流程控制」實現運算,所以寫程式就是在變數與運算間穿梭。流程控制可以由「循序」、「條件」與「重複」三種結構所組成,使用不同的程式碼搭配創造出各式各樣的運行結果。一般討論程式碼寫得好不好,可以從「記憶體的空間複雜度」與「CPU 的運算時間複雜」兩個方向著手,也就是如何管理好變數以及如何設計好流程。

資料結構這一門學科專注在如何利用一些抽象的結構有效在程式中儲存資料,換句話說,就是在討論怎樣更好的使用變數的方法。演算法的話則是搜集那些經典的方法,介紹那些常見的問題過去是如何設計出漂亮的方法來解決的,也可以視為是一種流程上的優化。所以,其實資料結構與演算法就是寫程式,資料結構或演算法其實就是程式碼經年累月淬煉出的精華,經過整理而成的武功秘笈,讓我們得以站在巨人的肩膀上寫出更好的程式碼。

看懂題目背後的設計思維

從「如何理解題目背後的設計思維」到「看懂題目背後的設計思維」,其實就是一種寫出更好的程式碼品質的過程。這些題目背後在意的其實就是那些曾經被優化過的歷程(例如資料結構或演算法),你能否站在巨人的肩膀上再持續往前呢?所以你說 LeetCode 題就是在考資料結構或演算法嗎?這句話我覺得對與不對,應該說是想考的是你能否從這些方法中習得「優化程式」碼的能力。

持續優化的思考過程

這個段落聽起來也許有點抽象,我們試著思考以下兩種場景:

① A 看完了題目馬上畫了關鍵字,心中想過我練過這個題目,然後埋首刻出了一個「一個非常漂亮」的寫法
② B 看完了題目皺了眉頭,向面試官釐清了一些疑問之後,然後寫出來了一個「好像沒有那麼厲害」的寫法

這邊我們可以明顯感覺到 A > B,但你丟出下一個問題「你可以怎麼優化這個程式碼呢?」後 …

① A 同學就說這是我在解答中看過最佳的算法,已經沒有更好的方法了。
② B 開始與面試官討論並且根據經驗提出這邊可以怎麼改、那邊可以使用另外一種方法,然後拼湊出優化後的程式碼。

從這兩個情境中可以明顯感覺 A 與 B 不太一樣,我也不確定那一種人在面試過程中比較容易受到青睞。不過更多的情況下,你很難真的刷到面試一模一樣的題目,所以我想能夠「從根據經驗提出優化的策略」是刷題中必須習得的技能之一。

 

(本文轉載自AC 資料工程師 WeiYuan 的文章 《LeetCode 解題的思考策略與解題地圖