怎樣學(xué)好算法
在我的四年大學(xué)里,花在學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)/算法的時(shí)間可以說(shuō)是最多的了,不過(guò)我并不后悔,因?yàn)樗惴ɑA(chǔ)的沉淀,給我后面的成長(zhǎng)帶來(lái)了很多幫助,所以今天這篇文章,帥地會(huì)根據(jù)自己的理解,談一談如何學(xué)習(xí)算法,如果你是這方面的小白的話,還是可以參考一下。
不過(guò),在寫(xiě)之前,我想先回答幾個(gè)問(wèn)題,或許對(duì)于那些剛?cè)腴T(mén)的同學(xué),有些許幫助。
一、簡(jiǎn)單回答幾個(gè)問(wèn)題
1、有必要學(xué)算法嗎?
很多過(guò)來(lái)人可能都會(huì)跟你說(shuō),算法沒(méi)必要學(xué),你又不是算法崗,工作其實(shí)就天天 crud,用啥都是封裝好的,學(xué)了也用不到,慢慢也就忘了。
這篇文章不是來(lái)跟你辯論有沒(méi)有必要學(xué)算法的,我就做個(gè)簡(jiǎn)單的回答,我的答案是,有必要學(xué)一學(xué),一個(gè)現(xiàn)實(shí)且勢(shì)利的原因,估計(jì)就是 ----- 大廠都喜歡考察算法了,不信你去問(wèn)問(wèn)剛剛參加過(guò) 2020 校招的同學(xué),我自己也參加過(guò) 2019 的秋招,算法考察,基本無(wú)處不在,如果想要獲得面試機(jī)會(huì),那么就得筆試,而筆試,大部分公司都是編程題,即算法題,而且,面試中也會(huì)經(jīng)常問(wèn)到算法,數(shù)據(jù)結(jié)構(gòu)。顯然,從找公司的角度看,不學(xué)算法你會(huì)失去很多面試的機(jī)會(huì)。然而,更重要的還是,程序 = 數(shù)據(jù)結(jié)構(gòu) + 算法,算法基本功打好,可以讓我們走的更遠(yuǎn)。
2、學(xué)算法好慢/好難,是我不夠聰明不適合學(xué)算法嗎?
答不是的,如果只是學(xué)習(xí)下常見(jiàn)算法,以后應(yīng)付下面試/筆試 + 分析下工作遇到的一些問(wèn)題,那么我覺(jué)得,還論不到天賦來(lái)做審判,這絕對(duì)不是雞湯,當(dāng)然,如果你想打 ACM,拿各種獎(jiǎng)的,那我就不大清楚了。
簡(jiǎn)單的說(shuō),學(xué)算法好慢/好難主要還是你代碼寫(xiě)的太少了,做的題太少了,雖然有些人學(xué)起來(lái)會(huì)慢一些,特別是入門(mén)那會(huì),但一旦過(guò)了這道坎,學(xué)起來(lái)就會(huì)快很多,所以,不要還沒(méi)學(xué)之前,或者學(xué)了一時(shí)半會(huì)覺(jué)得自己沒(méi)有 get 到點(diǎn),就否定自己。
二、入門(mén)數(shù)據(jù)結(jié)構(gòu)與算法
我是學(xué)習(xí)了《數(shù)據(jù)結(jié)構(gòu)與算法分析》這本書(shū)之后,再去學(xué)習(xí)各種算法思想 + 刷題的,所以我覺(jué)得,入門(mén)算法的第一步,是在你學(xué)會(huì)了一門(mén)語(yǔ)言之后(帥地當(dāng)時(shí)學(xué)的是 C 語(yǔ)言),靜下心來(lái)啃一本數(shù)據(jù)結(jié)構(gòu)與算法的書(shū)籍,例如我說(shuō)的《數(shù)據(jù)結(jié)構(gòu)與算法分析:xx語(yǔ)言描述版》,也可以是《大話數(shù)據(jù)結(jié)構(gòu)》等等。
怎么學(xué)這本書(shū)呢?
我覺(jué)得,對(duì)于剛剛?cè)腴T(mén)的選手來(lái)說(shuō),沒(méi)啥技巧,也不要迷戀于各種快捷的方法,咱們老實(shí)點(diǎn),當(dāng)個(gè)普通人,就跟著書(shū)學(xué),按照順序?qū)W就可以了,然后把里面例子的代碼,都至少打一遍,當(dāng)然,還需要跑通,結(jié)果要符合預(yù)期,如果不符合,就調(diào)試到符合預(yù)期。
注意,上面那句話我打了顏色,說(shuō)明這句話非常重要,千萬(wàn)不要覺(jué)得自己理解了,就不寫(xiě)代碼了,例如覺(jué)得自己理解了鏈表的增刪改,然后就不寫(xiě)代碼了,在編程這一塊,感覺(jué)自己理解,和成功實(shí)現(xiàn)且符合預(yù)期,特么就是兩回事。
之后一般每一章都會(huì)有習(xí)題,不需要全部做,自己挑幾道做就好了,就算是一眼就秒殺知道怎么做的,其實(shí)也可以實(shí)現(xiàn)一遍,如果不懂,可以找答案,但是一定要自己在電腦上把代碼敲打出來(lái),然后跑通。當(dāng)你把書(shū)上 90% 的代碼例子跑通,那么,這本書(shū)有一半的知識(shí),就是你的了。
這里我說(shuō)下,你們找的書(shū)籍,最好是有完整代碼實(shí)現(xiàn)的,因?yàn)橛行?shū)籍,為了具有通用性或者嚴(yán)謹(jǐn)性,是采用偽代碼來(lái)實(shí)現(xiàn)的,我不建議初學(xué)者用這類(lèi)書(shū)籍,因?yàn)槿菀滓荒樏杀?,代碼也不好跑通驗(yàn)證,所以如果你覺(jué)得自己是普通人,那么就找一本有完整代碼的書(shū)籍來(lái)看吧,然后乖乖把代碼的代碼敲打跑通起來(lái)。
不要眼高手低,當(dāng)你積累到一定的代碼數(shù)量,你就會(huì)慢慢來(lái)感覺(jué)了。
當(dāng)你學(xué)完了鏈表、隊(duì)列、棧、二叉樹(shù)、哈希表等最基本的數(shù)據(jù)結(jié)構(gòu),其實(shí)你就算入門(mén)了,這個(gè)時(shí)候其實(shí)你已經(jīng)具備了去 leetcode 刷題的能力了。不過(guò)在學(xué)習(xí)過(guò)程中,特別是到了圖那部分,會(huì)涉及到很多算法,例如最短路徑,深度搜索和廣度搜索…當(dāng)然,還有二叉樹(shù)的各種遍歷等。
如果你對(duì)遞歸一點(diǎn)也不懂,那么你會(huì)被虐的,腦袋也容易被爆棧,因?yàn)椋f歸真的無(wú)處不在,這也引出了我覺(jué)得入門(mén)算法非常重要的一個(gè)算法思想 --- 遞歸算法。
三、刷題之前的一些準(zhǔn)備
如果你連最基本的數(shù)據(jù)結(jié)構(gòu),例如鏈表,隊(duì)列,棧,二叉樹(shù)都沒(méi)有接觸過(guò),那么我是不建議你去 leetcode 刷題的,所以我上面先說(shuō)了先入門(mén)一下數(shù)據(jù)結(jié)構(gòu)與算法,當(dāng)你學(xué)習(xí)了這些基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)之后,其實(shí)已經(jīng)具備了刷題的能力了。
不過(guò),我還是希望你能在學(xué)習(xí)一些算法思想,一般就這幾種
1、遞歸
2、枚舉
3、貪心
4、回溯
5、動(dòng)態(tài)規(guī)劃
但是,其中最重要的,我覺(jué)得就是遞歸,其他的幾種算法,也都會(huì)有遞歸的影子,并且我剛才說(shuō)圖相關(guān)算法、二叉樹(shù)的遍歷等,也都包含遞歸的使用。
所以,在你刷題之前,或者在學(xué)習(xí)二叉樹(shù)、圖相關(guān)算法遇到遞歸的時(shí)候,我希望你能靜下心來(lái),去學(xué)一學(xué)遞歸,我也會(huì)告訴你,對(duì)于初學(xué)者,遞歸很難,我是被無(wú)數(shù)次折騰,無(wú)數(shù)次看答案似懂非懂之后,才突然醒悟了。
你不需要把它學(xué)的很精通,但是你要懂一些基本的遞歸題,知道遞歸是怎么一回事,例如最簡(jiǎn)單的斐波那契數(shù)列得會(huì)用遞歸做吧?階乘也得會(huì)吧(雖然不是最優(yōu)解)。
所以,死磕入門(mén)數(shù)據(jù)結(jié)構(gòu),可以學(xué)習(xí)下一些算法思想,而遞歸,你必須得入門(mén),至于動(dòng)態(tài)規(guī)劃、回溯,我覺(jué)得慢點(diǎn)學(xué)也沒(méi)有,可以后面刷題遇到時(shí)在學(xué),而枚舉、貪心,相對(duì)比較簡(jiǎn)單。
關(guān)于遞歸的,可以看我之前的一遍入門(mén)級(jí)的文章:為什么你學(xué)不會(huì)遞歸?告別遞歸,談?wù)勎业囊恍┙?jīng)驗(yàn)
評(píng)價(jià)還是很好,之后找些簡(jiǎn)單的題做,例如在 LintCode 那些 easy 的題(leetcode 和 lintcode 類(lèi)似,類(lèi)似于一個(gè)中文版,一個(gè)英文版)
四、如何刷題
終于,到了刷題這一部分了,如果要說(shuō)學(xué)算法的捷徑,那么刷題便是最好的捷徑,如果你刷的題很少,達(dá)不到一定的量,那么再多的捷徑,估計(jì)也沒(méi)啥用,只有在滿足一定題量的情況下,才適合來(lái)談?wù)撍^的技巧。
1、先說(shuō)一說(shuō)筆試
不過(guò)在刷題之前我想先說(shuō)一說(shuō)筆試,如果筆試不考算法,面試也不考算法,那么我可能在學(xué)習(xí)算法的這條路上,會(huì)少了很多的積極性,你可能會(huì)覺(jué)得我很功利,但是我覺(jué)得,帶著功利性的目的去學(xué)習(xí)算法,也是完全沒(méi)問(wèn)題的。
在校招的筆試中,其實(shí)這些筆試題還是挺難的,你在 leectode 可以做出 hard 級(jí)別的題,但在筆試中,可能連 medium 級(jí)別的都做不出,因?yàn)楣P試的題,都比較靈活,基本都會(huì)通過(guò)實(shí)際的例子來(lái)引出一道題,你可能不知道要使用哪種方法來(lái)做比較好,有些還是多種方法的結(jié)合。
對(duì)于筆試的題型,我之前也總結(jié)過(guò),無(wú)非是以下幾種
(1)、基本數(shù)據(jù)結(jié)構(gòu)的考察:這類(lèi)題我覺(jué)得是比較簡(jiǎn)單的,主要考場(chǎng)基本數(shù)據(jù)結(jié)構(gòu)的操作,例如二叉樹(shù)的層序遍歷,鏈表的逆序等,當(dāng)然,它不會(huì)直接告訴你,讓你來(lái)逆序或者遍歷。
(2)、某種算法思想的掌握:這類(lèi)題你掌握了某種算法思想,就會(huì)比較容易,如果不懂,那就涼涼了。例如動(dòng)態(tài)規(guī)劃、回溯、枚舉、深度/廣度、貪心、二分等。其中,我覺(jué)得動(dòng)態(tài)規(guī)劃考的挺多,還要就是 回溯+深度/廣度。
(3)、邊界條件的考察:這類(lèi)型的題,估計(jì)你一看就有思路,知道該怎么做,但是,它的邊界條件特別多,需要分很多種情況來(lái)討論,特別容易出錯(cuò),有時(shí)候會(huì)讓人陷進(jìn)去,越做越復(fù)雜,這類(lèi)題主要考場(chǎng)你的思維嚴(yán)謹(jǐn)程度。
(4)、找規(guī)律、數(shù)學(xué)公式:這類(lèi)型的題,主要是根據(jù)數(shù)據(jù)之間的一些關(guān)系,來(lái)找一些規(guī)律,進(jìn)而推出他們的通用公式,就像我們高中時(shí),找數(shù)列的同項(xiàng)一樣。
2、按分類(lèi)刷題
上面我列了筆試的題型,并且跟你說(shuō)了筆試是真的挺難的,那么對(duì)于我個(gè)算法小白來(lái)說(shuō),該如何做好呢?
我的建議是,分類(lèi)刷題,階段性總結(jié)。例如最開(kāi)始可以在 LintCode 按照鏈表/二叉樹(shù)/遞歸等這些標(biāo)簽來(lái)刷,因?yàn)檫@樣可以讓你深入掌握每一種方法。
當(dāng)然,筆試的題之所以難,是因?yàn)槲覀兺恢烙媚囊环N方法做好,或者說(shuō)具體屬于哪一種題型,那么還有必要分類(lèi)刷題嗎?
答是有必要的,只有當(dāng)你熟悉每一種題型,你才能靈活使用他們,進(jìn)而解決各類(lèi)復(fù)雜的題,這就如同你在練功夫的時(shí)候,前期你需要把每個(gè)招式都打扎實(shí)了,之后才能靈活把各個(gè)招式連接起來(lái),融合貫通。刷題也是一樣,前期先分類(lèi),把每個(gè)題型掌握起來(lái),后期咱們?cè)匐S機(jī)練習(xí),慢慢著就能靈活應(yīng)用了。
不過(guò),每次刷了一部分題型之后,我覺(jué)得還有必要做一些總結(jié),或者說(shuō)總結(jié)一些刷題模版,例如對(duì)于二分法查找,其實(shí)好幾種題型總結(jié)起來(lái),就是開(kāi)閉區(qū)間的組合,你可以把他們總結(jié)起來(lái),例如什么時(shí)候用開(kāi)區(qū)間,什么時(shí)候用閉區(qū)間。
有人可能會(huì)說(shuō),模版是死的,真的有必要總結(jié)嗎?
我覺(jué)得有必要總結(jié),但沒(méi)必要死記,總結(jié),只是加深你的理解,當(dāng)然,如果你在做題的時(shí)候,剛好記住了自己的模版,可以直接套上去,那肯定更好。但是,就算忘了也沒(méi)事,通過(guò)自己的總結(jié),你其實(shí)是知道怎么做的了,只是還需要你多花一點(diǎn)時(shí)間,快速模擬討論下各種情況,一樣能夠做出來(lái)的。
也就是說(shuō),最開(kāi)始刷題的時(shí)候,可以分類(lèi)刷題,并且階段性總結(jié),如果你是初學(xué)者,可以先從簡(jiǎn)單的題做起,例如我剛才說(shuō)的,簡(jiǎn)單的遞歸題,之后一些二叉樹(shù)、鏈表的題,因?yàn)槟憧赡軇倓倢W(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)不久,剛好可以加深你的理解。
五、刷題時(shí)的一些注意點(diǎn)
當(dāng)我們?cè)谧鲆坏李}的時(shí)候,可能會(huì)遇到兩種情況,一種是這道題,特么秒殺,一眼就懂思路;一種是,一臉蒙蔽,太難了吧。
一眼就懂思路,有必要做嗎?
我的答案是,有必要做。千萬(wàn)不要眼高手低,看著簡(jiǎn)單,做起來(lái)不一定簡(jiǎn)單,AC 之后,你還要去討論區(qū)看看大佬們是怎么做的,因?yàn)橛行┤说拇a,真的寫(xiě)的很簡(jiǎn)潔,看著就很舒服,咱們可以多學(xué)一學(xué)的,當(dāng)然,也有可能那個(gè)人就是你自己。
代碼寫(xiě)多了,有時(shí)候,你就會(huì)發(fā)現(xiàn)自己真的變強(qiáng)了,寫(xiě)起代碼來(lái),bug 也越來(lái)越少了,分分鐘 AC 一道題。
盡量最優(yōu)解
其實(shí)對(duì)于很多題,如果不看時(shí)間復(fù)雜度和空間復(fù)雜度,單單只是 AC,那還是很容易的,但是一提交,你的代碼可能只打敗了百分之幾的人,顯然我們是不能滿足于這種代碼的。
當(dāng)你做一道題時(shí),一開(kāi)始可以先暴力做,但后面,還得想想該如何優(yōu)化,想不出也沒(méi)事,可以討論區(qū)找空間/時(shí)間復(fù)雜度更低的代碼,或者直接搜索引擎搜索,一般都能搜到別人的代碼。
之后跟著別人的代碼,自己再實(shí)現(xiàn)一波,盡可能把最優(yōu)解的代碼實(shí)現(xiàn)起來(lái)。千萬(wàn)不要為了 AC 而 AC,不是 AC 的越多就越強(qiáng)的,當(dāng)你入門(mén)之后,更多的是要總結(jié)方法,尋找高效率的代碼。
六、總結(jié)
說(shuō)到算法的學(xué)習(xí)方式,對(duì)我來(lái)說(shuō),真的沒(méi)有什么捷徑之類(lèi)的,就是像我上面說(shuō)的,先找本書(shū)死磕入門(mén)數(shù)據(jù)結(jié)構(gòu),就跟著書(shū)的例子,把例子跑起來(lái)就好了,跑起來(lái)也不是一件簡(jiǎn)單的事情。之后就去接觸下一些算法思想,后面就可以分類(lèi)刷題了,刷題就是最好的捷徑了。
當(dāng)然,不要 AC 之后就完事了,應(yīng)該盡可能尋找最優(yōu)解,當(dāng)你積累了一定的題量,那么你真的會(huì)發(fā)現(xiàn)自己變強(qiáng)了,突然感覺(jué)遞歸也就那么一回事。
我學(xué)習(xí)算法時(shí),基本看書(shū) + 網(wǎng)上刷題,也很少看視頻,因?yàn)槲矣X(jué)得看視頻比較花時(shí)間,不過(guò)我之前在班里還是看到部分人喜歡看視頻的,至于看書(shū)好還是看視頻好,這個(gè)看個(gè)人喜好吧,也沒(méi)有說(shuō)哪種就一定更好。