上週在公司內部分享了自己練習演算法的心得和經驗,有小夥伴表示分享的內容給他帶來了價值,也很具備參考意義,於是就演算法寫成文章分享出來,近幾個月來,自己每週都會花1、2小時在 Leetcode 上面練習演算法,短短几個月下來也陸陸續續交出 40~50 的解題作業,算是一個小小的里程碑吧,以下是我最近的刷題記錄:
所有的解題記錄我傳到了的公開的 Github 專案,有興趣的可以訪問連結看看
偶爾在演算法群裡也有小夥伴總是在問:“刷題太難了,自己總是虎頭蛇尾,你們是怎麼堅持下來的 ?,有什麼方法嗎?”,其實很多人剛開始都是熱情滿滿,然後馬上就三分鐘熱度了,這其實也很正常,我也通過自己幾個月的探索和堅持,踩了不少坑,所以總結了不少我個人覺得行之有效的刷題經驗,跟大家分享一下,也避免大家後續再走彎路
總體大綱如下:(預計 5分鐘能讀完)
- 為什麼要練習演算法 ?
- 如何有效的練習演算法 ?
- 最後總結(關鍵的關鍵)
為什麼要練習演算法 ?
相信高手都明白演算法和資料結構是基本功,但是還有很多剛入行的新人不是很明白,我的個人觀點如下:
- 程式設計師的基本功:長期的練習演算法會讓你關注程式實現效率,既時間和空間複雜度,沒有演算法訓練的程式設計師只能寫出垃圾的程式碼
- 學習演算法不容易過時:例如 快速排序 是在 1960 年發明出來的,相比流行的框架,語言和新技術而言,演算法很難過時,更值得學習
- 更容易理解熱門的技術:例如比特幣(基於連結串列實現),區塊鏈如何保證安全交易(一致性共識演算法),等等……簡直不勝列舉
- 我們生活在一個概率的世界,如果有演算法和數學的思維,更容易在概率的世界中找到最優解,例如:打牌,做決策等等。。。
常用的程式語言大多都是對資料結構的封裝,所以很多面試官特別喜歡問以下的問題,例如:
- LinkedList 和 ArrayList 的區別,陣列和連結串列的擴容方式(考察你對陣列和連結串列的理解)
- HashMap 的內部結構(考察你對陣列 + 連結串列組合模式的理解)
- HashMap 為什麼使用紅黑樹(考察你對多種樹的資料結構的理解)
- 快速排序和歸併排序的區別(考察你對排序演算法的理解)
- 二叉樹的前序,中序,後序等(考察你對樹的遍歷的理解)
- 等等 ……
而且根據我瞭解的很多面試官其實根本不在乎你使用什麼程式語言,熟練使用什麼技術框架,這些只是“術”的層面,他們更加在乎你是否具備解決問題和抽象問題的能力,這些才更加具備長期的價值,更加接近“道”的層面,那麼如何考察呢 ?就是通過演算法,最好是通過在白板上給出解題的過程和思路,例如 Google 就特別喜歡這樣做。閒話扯的有點遠,我們下面進入正題
如何有效的練習演算法 ?
一:端正心態和學習態度
我覺得既然下定決定要去學習,那麼首先要調整的就是心態,這個社會大部分人都很浮躁,想要速成,但是學習是不可能速成的,需要先定好目標,然後一步一步向前行,首先我先推薦一本書**《異類》**:
這本書通過各種案例向大家傳遞了關於“一萬小時“的理論,而且不是勤勤懇懇的重複的一萬小時,必須是有目標,有階段,不斷按曲線成長的”一萬小時“,不然的話你也只是在感動自己而已,任何不經思考的學習和練習都是徒勞
如果要按照這個理論,就是說一個普通人,每天要花 3-4 小時的學習成長,差不多持續十年,你才有可能成為行業的高手和專家,所以要明白很多成功都不是偶然的,很多看似輕鬆達成某些成就的人,背後都付出常人無法理解的痛苦和努力,就像那些看上去身材很好的人,他們顯得很年輕,穿衣服也很好看,但是你沒有看到他們咬緊牙關在鍛鍊的時間,bob 大叔在《程式設計師的職業素養》一書中也說過一句話”任何專業人士都需要保持刻意連續,鋼琴家,醫生,律師,拳擊手,吉他手,樂隊等等,想要成為專業人士都必須保持刻意的練習“,所以大家應該明白學習沒有捷徑,想要成為專業人士,就要付出相應的努力
二:用玩遊戲的心態來學習
學習和看書那麼苦,那麼累,還要堅持那麼久,我們能不能想一些方法來減輕這種痛苦的感受?
答案是有的,也是我自己認為行之有效的一種,那麼就是:用玩遊戲的心態來學習(關於遊戲如何利用反饋機制讓人上癮的細節我就不詳細說明了,不瞭解的同學有興趣自行去 Google 瞭解反饋機制對大腦的刺激)
那麼具體如何操作呢 ?我簡單總結以下兩點:
回想一下你是如何成為 英雄聯盟/王者榮耀 高手的 ?
主要分以下幾步:
- 主動嘗試:進入遊戲隨便選擇一個英雄,簡單看看技能嘗試玩一玩
- 熟悉常用技巧:慢慢熟悉使用英雄所需要裝備,順便熟悉地圖,例如躲草叢
- 開始學習如何見招拆招:開始瞭解敵方英雄的技能和套路,並且熟悉對線的技巧
- 主動學習和練習:對於自己還未熟悉的英雄和技能反覆練習,慢慢形成長期記憶
以上是玩遊戲的步驟,那麼對於練習演算法,我們也可以用同樣的步驟:
- 主動嘗試:首先進入一個刷題網站(Leetcode、牛客等),隨便找幾個簡單題目嘗試一下
- 熟悉常用技巧:慢慢熟悉常見的資料結構,例如:陣列/連結串列/Queue/Set/Hash/Tree 等等
- 開始學習如何見招拆招:熟悉常用演算法題的套路和技巧,例如:雙指標/雜湊/位運算/反轉連結串列/二分/遞迴等等
- 主動學習和練習:對於自己還未熟悉的型別結構反覆練習,慢慢形成長期記憶
以上可以發現其實玩遊戲的思路可以用在演算法練習上,而且效果還不錯,不過僅僅做到以上幾點,還不能成為一個遊戲高手,通常如果你想在遊戲裡面成為高手還需要做到如下幾點,,我稱之為:
Feedback 持續反饋:
- 多打排位賽和天梯,跟自己等級相同的人對抗(對應到練習演算法就是找符合自己難度的題目,多練習)
- 多逛論壇和高手討論,多看前人的攻略並且自己不斷的總結心得(相同)
- 多看相關的比賽和直播,看高手之間競技,並且學習套路(相同)
如果能用以上的方法,再加上持之以恆的心態,應該就能成為一個(遊戲/演算法)的高手了
三:刷題的注意事項
上面總結了如何調整心態,下面我們講講在刷題階段的幾點注意事項
我自己剛開始也是沒有方法,上來就是一頓猛刷,結果刷了忘,忘了繼續刷,結果浪費時間不說,而且自己也沒有從中收穫到價值,這對於出題的作者和我本人都是雙輸的結果,而且後面跟幾個小夥伴在微信群裡面組隊刷題的時候,看到很多小夥伴都重複犯了這個錯誤,甚至有的人從此對演算法失去興趣,可惜可惜……
對於初學者練習演算法,我有以下幾點建議,簡稱 CPCT 法:
- 認真審題 Clarification:一定要明確作者出題的意圖,複雜度的要求等等,例如作者想考察你的二分查詢,你非要用暴力破解,雖然也能解題,你失去這道題最精髓的內容
- 找到最優解 Possible Solution:上面也說了解題的程式碼要符合作者對時間/空間的要求,不過這裡我建議你分兩步走,很多經典的演算法題是經過很多年才總結出現了,除非是少部分的天才,大部分人很難在短時間內找到最優解,第一步可以先嚐試用最簡單的方法把題目解出來,然後再逐步的優化程式碼,直到達到作者要求的最優解,重複一下上面的觀點,每道題的最優方案才是這道題最精華的內容,也是作者最想考察的知識點
- 反覆練習 Coding:多寫程式碼,多練習,演算法就是三分學,七分練
- 覆蓋測試 Tests Cases :當提交的 Case 被判定不通過的時候,是一件很痛苦的事情,所以提交 Case 前儘量在本地最好足夠的測試,這點也可以用在我們平時寫程式碼的習慣上
四:介紹一下複雜度
有一個很經典的演算法問題,就是要計算 1+2+3+4....+100 的總和的方法:
因為 100 也不算很大,很多同學估計這樣計算:
- 先計算 1 + 2 得到 3
- 再用上面的結果計算 3 + 2 得到 5
- 再用上面的結果計算 5 + 3 得到 8
- 以此類推,重複 100 次
勉強計算 100 次後,我們得到 5050 的最終結果,
我們再把問題擴大一下,如果要計算的值不是 100, 而是 1 + 2 + 3 + 4 .... 一直到 1000, 或者 10000 呢 ?如果值是 1000,那麼就要重複計算 1000 次,如果值是 10000 ,那麼就要重複計算 10000 次,負責計算的人估計會崩潰了(沒錯,計算機也是這麼想的),隨著 N 越大,要計算的次數越多,這種需要計算 N 次的演算法,我們評估它的時間複雜度為:O(n)
這時候還在讀小學,並且不願意透露姓名的高斯同學,發現的計算的規律,並且總結的求和公式:Y = n * (n + 1) / 2
使用這個公式不管要計算的值有多大,得到的結果都是相同,而且永遠只需要計算一次,這種方法我們評估它的時間複雜度為:O(1)
這也是演算法的目的:在保證結果正確的同時,將時間複雜度和空間複雜度降到最低
常見的大O表示法和常見的術語:
從優 >> 差的複雜度排序:
- O(1):常數複雜度
- O(log n):對數複雜度
- O(n):線性複雜度
- O(n log n):nlogn 階
- O(n^2):平方
- O(n^3):立方
- O(2^n):指數
- O(n!):階乘
最後總結
關鍵的關鍵
- 多練習,刻意練習(找到自己缺陷,反覆練習)
- 推薦網站 LeetCode (按照型別分組,按照型別練習)
- 切題的方法(吃透所有解法,找到最優解)(注意時間/空間複雜度)(使用你熟悉的語言和編輯器)
- Feedback 持續反饋(主動學習)
- 重在實踐:三分學,七分練(切莫貪多,貴在堅持)
- 組隊打卡:找到有共同興趣的朋友一起練習,更容易堅持(想要加入演算法打卡群的請給我留言)