大綱
前言
多數人對 LeetCode 的反感源自何處
用「專家理論」來解釋為何會產生不滿
The Valuable Parts
fundamental DSA
complicated prob solving skills
learning the programming language
test case driven/benchmarking/拆分式學習
Revisit#1 如何學習 LeetCode
同時 pickup 一門系統語言,來幫助你學習
先去學好 DSA(也可以邊寫邊學)
主題式學習,建立自己的學習卡片
避免「抄解答」
Revisit#2 學習上的本質
常人能通靈寫出的極限,叫做 easy
與其追逐「數量」,不如追逐自己的「品質提升」
前言
LeetCode 在軟體工程師這行業中,可說是個永不消散的話題坑,不但可以延伸到「競程」、「寫程式的本質」、「FAANG Prestige」、Startup vs. 大公司,以及實用主義等等。
受眾的觀點也經常呈現兩極,有人堅持刷題,有人則對此厭惡至極。
[多數人對 LeetCode 的反感從何而來呢?]
認為整個行業的面試以競程標準無限上綱,導致軟體工程師領域內的內卷和內耗。
認為 LC/競程本身像 riddle questions
LC medium~hard 以上的問題,初見很容易寫不出來
LC 的練習本身像無止盡的背題,寫了就忘,寫不出來不知道從何學、很挫折
認為競程本質上就與 software engineer 關聯度不高
[用「專家理論」來解釋為何會有厭惡]
認為 LC/競程本身像 riddle questions
LC medium~hard 以上的問題,初見很容易寫不出來
LC 的練習本身像無止盡的背題,寫了就忘,寫不出來不知道從何學、很挫折
之所以感覺到不快樂,是因為客觀上真的沒進步!
為何會沒進步呢,一萬小時(aka 專家理論、刻意練習),有給我們很好的解答方式:
TLDR: 要在任何事情上達到基本專業水準,需要花費足夠的時間,同時確保以下(練習)條件全部滿足:
練習者必須要能時常練習,並且有 feedback
feedback 必須是非隨機,容易成就 pattern recognition 的(valid env)
feedback 要能夠及時
要能獲取「有效練習」(須有足夠挑戰性,使投入能有效進步)
而初寫 LeetCode 會遇到的障礙,經常是 2. 與 4.
(2.) 在寫 riddle/smart qn 時,成功與否像是翻硬幣一樣難以預測
(4.) LeetCode 本身問題的編排方式、面試的考法,對初學者來說難以有所進步
為什麼初學者難以有所進步呢?主要是因為LeetCode的大多數問題都有一定的「知識要求」
如果你對 stack, queue, graph,甚至是如何實作經典的數個演算法一無所知,那寫每題 medium/hard,都像業餘球手對上職業球員一樣,每次失敗都讓你一無所獲,永遠感到沮喪大於成就
The Valuable Parts
以下,筆者會列出些,自己認為「普世皆通」的 LC 練習優點
[fundamental DSA]
在越接近系統層級的場域開發時,對基礎資料結構,記憶體、跑分本身的理解就佔比越高
舉例來說,今天寫 Rust/C++/Go 的開發者,如果他沒有具備不靠 lib 寫出基礎資料結構,及依照 LC-easy 等級的需求寫出演算法,那確實是很難讓人信服他的 system programming 能力
[complicated problem solving skills]
就如同面試寫久會被 hard 碾一樣,工作上不論是需揉合多個棘手 constraints 來設計出架構,及臨時要依照某篇論文的敘述開發出演算法
總有一天會遇到一個當下打不過、也完全不知如何 approach 的魔王關卡。而筆者認為,這時才是 學習者所有練習派上用場之時。
(筆者認為)在 staff engineer 這層,就開始會是「能解未知問題」成為硬需求的階段,而如果能有效以 LC 來做基礎培養,他是個很好的工具
[learning the programming language]
在學習系統程式語言(Rust/C++/Go)時,開發 application 經常成本是較高的
而 LC 的練習方式(自己想出 test case、效能差時 benchmark)其實是個與「時常練習」契合度很高的素材
Revisit - 如何 Learn LeetCode the Hard Way
知道了學習常見的地雷,以及 LC 本身是在「學什麼」,以下是筆者對初寫 LC 的人的 advice
[#1 挑選一門系統語言來寫 LeetCode]
如上所述,LeetCode 練習的能力在 system programming 上是有最顯著相關的,因為 Rust/C++/Go,不但在練習過程中,最能體會到這些語言的價值與光彩,在面試、實務上也最容易用到練習的成果
[#2 先至少上過一門 DSA 課程,再來寫 LeetCode]
LeetCode 本質上包含的題目類型,幾乎都是大學資料結構、演算法參考書的延伸
如果你對以下名詞(如:recursion、merge sort、tree、hash map、graph、DFS、BFS、union-find、minimum spanning trees、Dijkstra's algorithm)全都一無所知
那 LeetCode 非常高機率對你會是「無效練習」,easy, medium, hard 幾乎所有問題都是從以上 DSA 的教材知識延伸的
Ex. 叫一個沒聽過 Shunting Yard Algorithm 的中學生,自己想出一樣的解法,高機率是無效練習的
[#3 主題式寫題,建立自己的學習卡片]
與 #2 相呼應,LeetCode 大多數問題都有承先啟後的知識在
Ex. easy 通常是基於單個資料結構、簡單演算法;medium 通常是簡單演算法 + 單個資料結構的應用題(多數教科書上會學到);hard 則可能是較繁瑣的 medium、或需要自己想出搭配的 smart question
因此,最能達成有效練習的方式是「為自己設計課綱」和「以主題為單位練習」
LeetCode 的 explore https://leetcode.com/explore/ 與 problem tag 就是很好的指引 https://leetcode.com/tag/hash-table/ (初寫者不容易意識到它的重要性)
遇到知識量不足的區塊,主題式學習也能讓你自己不亂了陣腳(花一週去看參考書即可),也會少讓你在無效練習的題目上浪費太多動力
[#4 寫不出來,也不要去看詳解]
這可能是其中一個反直覺的建議,畢竟就學過程中「不知道答案就看、看不懂就背」,不是學生基本技能嗎?
但我們回頭想,也是這種學習習慣,導致十二年國教中的知識多數人「學完就忘」、「考應用申論就死機」。在寫程式中,這會直接導致你根本沒練習到最重要的 complicated problem solving skills
因此,面對寫不出來的題目,你該做的是放下心中想被解答的慾望,可以上網搜索此題有哪些關聯的資料結構、更簡單的題目版本。看詳解是有很大副作用的,因為看到答案的那當下,你等於放棄了有機會自己系統化學會此題的機會。
結語 - 學習的本質
寫了這篇,筆者的用意只是想分享數個容易學習走彎路的坑(事實上我是寫題小白)
而「常人(有缺 DSA 知識)能靠通靈寫出的極限,是 LeetCode easy」,這句 motto,也是我自己在過往的血淚中領域出的。
而學習的本質,我認為不外乎就是讓自己變聰明、產生質變、讓未來能真正解出未知問題。以這樣的態度來進行(任何)的投入,就很難沒有成果了。
[額外資源]
在 LeetCode 上找不到題型、被鎖在 paywall 後的題目,有些可以靠 https://www.lintcode.com/problem 找
對於如何以較健康的心態看待競程,可以參考這篇 https://leetcode.cn/circle/discuss/BcuYOM/
如何基礎的劃分 DSA 主題,可以參考某台大課程的 HackMd https://hackmd.io/@arthurzllu/SkZBc7GoI#Analysis-of-Algorithms
本文書寫的構思圖
https://www.youtube.com/watch?v=GPIuPRqDGG8