天天看點

LeetCode 按照怎樣的順序來刷題比較好?

LeetCode 按照怎樣的順序來刷題比較好?

首先,這個問題是不完整的。問題隻問了一半,事情也隻能做到一半。我們要看到更深層的需求,比别人多想一點,才能比别人優秀一點。

刷題效果 = 刷題路徑 * 刷題方法。

那麼這個問題,就可以拆解成兩個答題要點:

1)按照怎樣的順序來刷題?

2)刷題的正确姿勢是什麼?

一、按照怎樣的順序來刷題?

如果你想要開始刷題,那麼第一步就是:打開 LeetCode 官網,點選标簽,選擇一道順眼的題目開始刷。

注意,在這過程中,不要左思右盼,不要去搜尋與思考到底是刷 LeetCode 好還是去牛客網刷劍指 Offer 好。

我作為一名算法小白的時候,就犯了這個錯誤:在粗略的學習基本的資料結構與算法後,準備開始刷題,總想着找一個最有效最好的刷題平台。

一會在 LeetCode 題解區逛逛,一會在牛客網看看面經,結果就是整個人煩躁不安,焦慮迷茫,題沒有刷幾道,羨慕嫉妒恨卻增加了幾分:别人的代碼怎麼這麼簡潔 ? 别人的 Offer 怎麼這麼亮眼?

經過痛定思定之後,我開始自我剖析自己想好好刷題卻無效的原因:

1、沒有接受自己是算法小白的事實

我那時候隻是按圖索骥般的稍微系統的學習了基礎資料結構與算法知識,根本沒有真正的利用這些知識去處理問題。

在刷題的過程中,總想證明自己可以的,别人可以寫成簡潔高效的解題方法,我也要!于是去不停的找題證明自己,結果就是越刷越沒有效果,自己根本就看不懂題目考察的資料結構與思想。

整個人完全奔潰,不刷題了,不準備算法面試了,不準備跳槽了!

後來我不停的告誡自己:作為一名非科班的程式員,肯定比不上他們呀,如果随随便便的學了一點就能刷題順利,那别人大學四年不白學了!

是以前期先接受自己的思考方式,暴力解法其實也是一種有效的解法。

2、沒有合理的刷題

我隻是盲目的追求刷題的數量,即使刷了 200 道,腦中依舊一團漿糊。

後來才明白,吃透一道題目比亂刷十道題目更有價值。

經過不斷的摸索與試驗,形成了自己的一套刷題路徑。

  1. 自己的解法
  2. 網上好的解法
  3. 自己的解法可以改進的地方
  4. 不停的優化
  5. 尋找相同的題型重複練習
  6. 總結

每一個題目都經過至少一遍這樣的疊代,徹底吃透一道題進而掌握一種題型。

以一道極其簡單的動态規劃題為例 ,LeetCode 第 70 号問題:爬樓梯。

LeetCode 按照怎樣的順序來刷題比較好?

當時的我根本不知道動态規劃的相關概念,什麼狀态,什麼轉移方程,通通沒聽過。

沒錯,當時就那麼菜!

二話不說,直接使用暴力解法。

class Solution {
    public int climbStairs(int n) {
        return calcWays(n);
    }
    private int calcWays( int n ){
        if ( n == 1) return 1;
        if ( n == 2) return 2;
        return calcWays(n-1) + calcWays(n-2);
    }
}      

很明顯,無腦的遞歸暴力解法包含了大量的重複計算,送出上去直接标紅提示超出時間限制。

後來看了網上高票答案的分析,知道了備忘錄的概念,于是很容易寫出優化後的代碼。

//采用備忘錄的方式來存子問題的解以避免大量的重複計算

class Solution { 
    int[] memo; 
    public int climbStairs(int n) { 
        memo = new int[n+1]; 
        return calcWays(n); 
    } 
    private int calcWays( int n ){ 
        if ( n == 1) return 1; 
        if ( n == 2) return 2; 
        if (memo[n] == 0) 
           memo[n] =  calcWays(n-1) + calcWays(n-2); 
        return memo[n]; 
    } 

}      

再後來,發現備忘錄是自頂向下的方式,稍許變動,修改為自低向上的遞推方式就是動态規劃的形式。

class Solution { 

    public int climbStairs(int n) { 
        int[] memo = new int[n+1]; 
        memo[0] = 1; 
        memo[1] = 1; 

        for(int i = 2 ; i <= n; i++){ 
            memo[i] = memo[i-1] + memo[i-2]; 
        } 
        return memo[n]; 
    } 
}      

按照這樣的刷題路徑下來,發現對這類題型有了初步的思考途徑,有了發力點,再也不會一籌莫展:看題懵逼半小時,Coding 隻會按空格。

徹底搞懂這題後,就需要找到類似的題型,然後不斷的重複練習:最小路徑和、整數拆分、完全平方數、解碼方法、不同路徑、不同路徑 II。

通過這些練習,尋找題目中的共同點,為什麼這類題型都可以這樣思考呢?

慢慢的,知道了最優子結構、狀态轉移方程、重疊子問題的概念,不知不覺動态規劃的知識點已經掌握了 80%。

再遇到更高難度的動态規劃的題目時,心裡也明白,一時半會沒做成無法就是最優子結構、狀态轉移方程、重疊子問題沒有理清楚。

這樣長期堅持下來,接觸新的題型時也可以從容不迫的思考。

二、刷題的正确姿勢是什麼?​​

原文作者:Rocky0429

很多人開始他的刷題之路因為各種各樣的原因:進大廠、研究所學生複試或者參加競賽拿牌,當然也可能是因為喜歡。其實不管你抱着何種目的開始,我希望你能一直在刷題這條路上走下去,畢竟除了提高自己解決問題和寫代碼的能力這種顯而易見的好處,也能當作無聊時候的一種消遣...

其實随着刷題的深入,我發現刷題其實就是分為兩步:

  1. 第一步有思路,即知道用哪種姿勢怎麼解題;
  2. 第二步是實作,即将你的思路轉化為代碼。

接下來我所有的廢話都是圍繞這兩步來展開。

0x01 有思路

先說第一步:有思路。

算法題刷多了,你就會發現,最後其實在你腦子裡記住的不是實作這道題的代碼,而是解這道題的思路。

當我們刷了幾百道幾千道算法題的時候,你不可能記住每道題的代碼,但是你可能知道這道題的思路,也就是出現類似“這道題我見過,我知道用這樣那樣的方法可以做出來”。有了思路,其實把它實作出來就是自然而然的事兒了,當然可能有人說知道了思路也不知道怎麼實作,現在我先不說,這是我們下一步要講的問題。

上面說的是我們要走到的目的地,那如何走上這條路,進而到刷題刷到思路“泉湧”呢?其實很簡單,我們從小到大一直在被動習慣的四個字:題海戰術。

題海戰術,說白了就是多刷題,見多才能識廣。

但這裡的多刷題,不是指多瞎刷題,而是有方法的去刷。至于刷題的網站我已經在文章的開頭放連結了,不知道去哪找題的可以看一下。

首先說什麼是瞎刷題,就是看到一道刷一道,這是很多剛開始刷題的同學容易犯的毛病。

有的追求數量,刷了一堆簡單題,沉迷在 AC 的快感中不能自拔,在深深的自我感動中依然菜的扣腳;

有的追求無腦,看到一道題就去網上搜答案,以為會解決問題,實則搜到了還看不懂,正好一勞永逸,給自己下了不是這塊料的斷言,成功的做到了開始即結束。

别問我為什麼知道,我才不會告訴你當年我就是這樣...

其實怎麼用正确的題海戰術,在我看來,其實也還是兩步,第一步多題一解,第二步一題多解。

當然在此之前,我覺得你得先搞明白什麼是時間複雜度和空間複雜度,不然不懂這些名額,你也不知道算法對于你目前題目的優劣。

0x01-1 多題一解

多題一解,就是把多種同類型的題先放在一起來做,也就是俗稱的刷專題。下面是我當年刷題的一部分分類的截圖:

LeetCode 按照怎樣的順序來刷題比較好?
LeetCode 按照怎樣的順序來刷題比較好?

很多大佬說做題要追求完美,一道題來 N 種姿勢,但是對于剛開始起步的同學來說,一道題帶着多解的思想包袱去刷,本身就是一種負擔。你很難指望初學者能一上來就一題多解,沒那麼多見識,腦闊裡沒儲備那麼多的算法類型,能夠暴力破解且跑通就已經是燒高香了。

這裡再多提一嘴,關于網上搜答案這件事,答案可以搜,但是不要上來一看題,感覺自己不會就立馬搜答案,要嘗試思考,多在草稿紙上寫寫畫畫,實在想不出來再去搜。

搜到的答案我不希望你去看别人的代碼,按照别人的代碼一步步的寫出來其實本身沒有多大意義,真正有意義的是别人的思路,通過别人的思路來自己實作出現,這才是最應該做的。

這樣做的好處是,你可以很快了解一種類型題目的做題方法,加深對某類算法的了解,總結出做題的套路,這算是一種抽象的概括能力。很多時候你就會發現,題目不過是在某類解決辦法方面做加法減法。

0x01-2 一題多解

其實這個不用刻意去追求一題多解的能力,刷的專題多了,碰到的題目多了,自然而然你碰到一道題的時候腦袋裡就會有想法,覺的可以這樣做,也可以那樣做,這個時候你就可以對比不同的時間複雜度和空間複雜度,選擇目前的最優解法。

說一題多解,其實就是希望你在碰到一個問題的時候能夠多想一步,一步一步再一步,不同次元不同姿勢都嘗試一下。剛開始這可能比較難,畢竟這涉及到一個改變,因為人都是有惰性的,畢竟隻求一解比自找麻煩的求多解舒服多了...

題目見的多了你就會發現,很多時候你會碰到這種情況:A 題你有 5 種方法去解決它,改變它的某一個條件變成 B 題,作為 A 題的相似題 B,可能這個時候你照搬 A 的解法來解決 B,你隻剩下 3 種或者更少的解法可以解決 B 題,如果你隻會 1 種解法,剛好這種解法失效,那你就隻能再另想它法。

是以一題多解的好處也是顯而易見的,就相當于你的手裡多了很多的選項,選項多了不管你在面試或是其它時候,都能手裡有牌可打。

在這裡我又要多提一嘴,追求一題多解并不意味着“不擇手段”的追求題解數量的堆疊,也就是不要過分追求所謂奇淫技巧的解法,而這恰恰是許多同學容易犯的毛病,錯誤的認為了奇淫技巧等于水準高超,在我看來這個除了能引來别人一句卧槽的驚訝,進而帶來一點内心虛榮心的滿足以外,其餘的用處不大,看個熱鬧就得了。

畢竟魯迅先生曾經說過:“Use your best judgement”。

當然我也不是全盤否定技巧,但是你連個兩三百道題都沒刷完,你就在這給我講你要技巧,我會認為你是在耍流氓...

0x02 實作

一道題有了思路,其實這道題的 90% 你已經解決了,把它實作出來按理來說就是自然而然的事兒了。

當然可能有同學知道了思路,但是就卡在這 10% 不知道怎麼實作上,那這就是你寫代碼的能力問題,其實一樣的,這就是不熟練,不熟練的原因就是練少了。

其實這個問題的唯一解還是所謂的“題海戰術”,多練習,唯手熟爾。

剛開始的時候不管是書上的例題,一些簡單的水題或者你想實作的一個簡單的東西,按照你的想法寫出來或者看一遍别人怎麼寫的,自己再一步一步的默敲,不要怕麻煩,一定要自己動手,不要看會了,我們都知道看會了其實不是真正的會。但是慢慢當你習慣了這種方式,你的代碼能力會潛移默化的變強。

别問我為什麼知道,我難道要說作為一個當年上了大學半年還沒寫過一次超過 20 行的代碼的男人,經過一個寒假以後,能切百十行代碼的題?

也太丢面兒了吧,說好的整個學霸人設呢...

0x03 第三步

咦?不是隻有兩步嘛,哪來的第三步?

嘿嘿,總得給能堅持看我說廢話看到這裡的同學開個小小竈不是...

其實還有兩點是我想說的,而且這兩點是我覺得在整個過程中最重要的。

0x03-1 做總結

怎麼說呢,做總結這件事的好處,誰做誰知道,不信你就試試...

每道題有每道題的總結,每種類型的題有某類題的總結,千萬不要怕麻煩,雖然剛開始的時候确實會很麻煩...

每每回想起來,我最後悔的就是在我剛開始刷題的時候沒有做總結。當年集訓隊老師告訴我們每道題做完都要把題解釋出到 CSDN 上,記錄自己的思路,解題方式和代碼。這件事乍一聽我覺得太麻煩,覺得“有這個時間我多刷道題它不香嘛”,一直當作耳旁風。

後來真正開始在 CSDN 上發題解,并不是我突然頓悟,而是集訓隊老師看我們太懶,強制執行,然而這個強制,在經過初期的不适以後,慢慢的讓我形成了做什麼都要總結記錄的習慣,一下子就寫了 6 年。

0x03-2 保持熱情

保持熱情,不僅僅是能堅持,而要在堅持上最好能帶有一點興趣。刷題真的是一個很漫長的過程,如何在這個過程中能堅持下去真的很難做到...

我覺得你最好有一個最終的目标,這個很多開始刷題的同學肯定都有,不然沒人閑着沒事找事去刷題,有了最終的目标朝着這個方向去努力,同時把這個過程分成一部分一部分,比如拿刷專題來說,我這段時間刷連結清單,下段時間刷貪心,再下段時間刷 dp...

将目标量化為可衡量的每一段,自己有了掌控感,一步一步的向着最終的目标前進,知道自己離着還有多遠,不至于半途而廢。

拿我自己來說,當年搞 ACM,半年以後我已經準備放棄了,那段時間完全迷茫,覺得自己水準很差,沒有機會去參加比賽,不可能拿到獎牌。那段時間我開始去尋找别的出路,去參加 Python 的社團,準備轉去做項目。

渾渾噩噩了一圈,最後還是回去做 ACM,一方面是不想讓自己半年的努力付諸東流,對拿牌子的執念,更多的是我發現坐在那寫項目和做題比起來,我更喜歡 AC 的快感。

0x04 寫在之後

以上就是我的一點點經驗,其實沒有什麼新鮮的,有點啰嗦,也不一定能讓你有什麼進步。我一直覺的隻要我們付出了時間和努力,開始向更好的方向邁出第一步,我們解決問題和寫代碼的能力就會潛移默化的提高。

在這個過程中,收獲的遠比去解決問題更有成就感,當然這種感同身受更多的需要你自己在這個過程中去體驗。

可能末了整篇文章最有價值的隻有四個字 - 題海戰術。