雖然刷題一直飽受詬病,不過不能否認刷題確實能鍛鍊咱們的編程能力,相信每一個認真刷題的人都會有體會。如今提供在線編程評測的平臺有不少,比較有名的有 hihocoder,LintCode,以及這裏咱們關注的 LeetCode。html
LeetCode 是一個很是棒的 OJ(Online Judge)平臺,收集了許多公司的面試題目。相對其餘 OJ 平臺而言,有着下面的幾個優勢:python
題目所有來自業內大公司的真實面試c++
不用處理輸入輸出,精力全放在解決具體問題上git
題目有豐富的討論,能夠參考別人的思路github
精確瞭解本身代碼在全部提交代碼中運行效率的排名面試
支持多種主流語言:C/C++,Python, Java算法
能夠在線進行測試,方便調試shell
下面是我刷 LeetCode 的一些收穫,但願可以引誘你們有空時刷刷題目。編程
波利亞用三本書:《How To Solve It》、《數學的發現》、《數學與猜測》)來試圖闡明人類解決問題的通常性的思惟方法,總結起來主要有如下幾種:數組
時刻不忘未知量
。即時刻別忘記你到底想要求什麼,問題是什麼。(動態規劃中問題狀態的設定)
試錯
。對題目這裏捅捅那裏搗搗,用上全部的已知量,或使用全部你想到的操做手法,嘗試着看看能不能獲得有用的結論,能不能離答案近一步(回溯算法中走不通就回退)。
求解一個相似的題目
。相似的題目也許有相似的結構,相似的性質,相似的解方案。經過考察或回憶一個相似的題目是如何解決的,也許就可以借用一些重要的點子(比較 Ugly Number 的三個題目:263. Ugly Number, 264. Ugly Number II, 313. Super Ugly Number)。
用特例啓發思考
。經過考慮一個合適的特例,能夠方便咱們快速尋找出通常問題的解。
反過來推導
。對於許多題目而言,其要求的結論自己就隱藏了推論,無論這個推論是充分的仍是必要的,都極可能對解題有幫助。
刷 LeetCode 的最大好處就是能夠鍛鍊解決問題的思惟能力,相信我,如何去思考自己也是一個須要不斷學習和練習的技能。
此外,大量高質量的題目能夠加深咱們對計算機科學中經典數據結構的深入理解
,從而能夠快速用合適的數據結構去解決現實中的問題。咱們看到不少ACM大牛,拿到題目後當即就能想出解法,大概就是由於他們對於各類數據結構有着深入的認識吧。LeetCode 上面的題目涵蓋了幾乎全部經常使用的數據結構:
Stack:簡單來講具備後進先出的特性,具體應用起來也是妙趣橫生,能夠看看題目 32. Longest Valid Parentheses。
Linked List:鏈表能夠快速地插入、刪除,可是查找比較費時(具體操做鏈表時結合圖會簡單不少,此外要注意空節點)。一般鏈表的相關問題能夠用雙指針巧妙的解決,160. Intersection of Two Linked Lists 能夠幫咱們從新審視鏈表的操做。
Hash Table:利用 Hash 函數來將數據映射到固定的一塊區域,方便 O(1) 時間內讀取以及修改。37. Sudoku Solver 數獨是一個經典的回溯問題,配合 HashTable 的話,運行時間將大幅減小。
Tree:樹在計算機學科的應用十分普遍,經常使用的有二叉搜索樹,紅黑書,B+樹等。樹的創建,遍歷,刪除相對來講比較複雜,一般會用到遞歸的思路,113. Path Sum II 是一個不錯的開胃菜。
Heap:特殊的徹底二叉樹,「等級森嚴」,能夠用 O(nlogn) 的時間複雜度來進行排序,能夠用 O(nlogk) 的時間複雜度找出 n 個數中的最大(小)k個,具體能夠看看 347. Top K Frequent Elements。
咱們知道,除了數據結構,具體算法在一個程序中也是十分重要的,而算法效率的度量則是時間複雜度和空間複雜度。一般狀況下,人們更關注時間複雜度,每每但願找到比 O( n^2 ) 快的算法,在數據量比較大的狀況下,算法時間複雜度最好是O(logn)或者O(n)。計算機學科中經典的算法思想就那麼多,LeetCode 上面的題目涵蓋了其中大部分,下面大體來看下。
分而治之:有點相似「大事化小、小事化了」的思想,經典的歸併排序和快速排序都用到這種思想,能夠看看 Search a 2D Matrix II 來理解這種思想。
動態規劃:有點相似數學中的概括總結法,找出狀態轉移方程,而後逐步求解。 309. Best Time to Buy and Sell Stock with Cooldown 是理解動態規劃的一個不錯的例子。
貪心算法:有時候只顧局部利益,最終也會有最好的全局收益。122. Best Time to Buy and Sell Stock II 看看該如何「貪心」。
搜索算法(深度優先,廣度優先,二分搜索):在有限的解空間中找出知足條件的解,深度和廣度一般比較費時間,二分搜索每次能夠將問題規模縮小一半,因此比較高效。
回溯:不斷地去試錯,同時要注意懸崖勒馬,走不通就換條路,最終也能找到解決問題方法或者知道問題無解,能夠看看 131. Palindrome Partitioning。
固然,還有一部分問題可能須要一些數學知識去解決,或者是須要一些位運算的技巧去快速解決。總之,咱們但願找到時間複雜度低的解決方法。爲了達到這個目的,咱們可能須要在一個解題方法中融合多種思想,好比在 300. Longest Increasing Subsequence 中同時用到了動態規劃和二分查找的方法,將複雜度控制在 O(nlogn)。若是用其餘方法,時間複雜度可能會高不少,這種題目的運行時間統計圖也比較有意思,能夠看到不一樣解決方案運行時間的巨大差別,以下:
固然有時候咱們會犧牲空間換取時間,好比在動態規劃中狀態的保存,或者是記憶化搜索,避免在遞歸中計算重複子問題。213. House Robber II 的一個Discuss會教咱們如何用記憶化搜索減小程序執行時間。
對一個問題來講,解題邏輯不會因編程語言而不一樣,可是具體coding起來語言之間的差異仍是很大的。用不一樣語言去解決同一個問題,可讓咱們更好地去理解語言之間的差別,以及特定語言的優點。
C++ 以高效靈活著稱,LeetCode 很好地印證了這一點。對於絕大多數題目來講,c++ 代碼的運行速度要遠遠超過 python 以及其餘語言。和 C++ 相比,Python 容許咱們用更少的代碼量實現一樣的邏輯。一般狀況下,Python程序的代碼行數只至關於對應的C++代碼的行數的三分之一左右。
以 347 Top K Frequent Elements 爲例,給定一個數組,求數組裏出現頻率最高的 K 個數字,好比對於數組 [1,1,1,2,2,3],K=2 時,返回 [1,2]。解決該問題的思路比較常規,首先用 hashmap 記錄每一個數字的出現頻率,而後能夠用 heap 來求出現頻率最高的 k 個數字。
若是用 python 來實現的話,主要邏輯部分用兩行代碼就足夠了,以下:
num_count = collections.Counter(nums) return heapq.nlargest(k, num_count, key=lambda x: num_count[x])
固然了,要想寫出短小優雅的 python 代碼,須要對 python 思想以及模塊有很好的瞭解。關於 python 的相關知識點講解,能夠參考這裏。
而用 C++ 實現的話,代碼會多不少,帶來的好處就是速度的飛躍。具體代碼在這裏,創建大小爲 k 的小頂堆,每次進堆時和堆頂進行比較,核心代碼以下:
// Build the min-heap with size k. for(auto it = num_count.begin(); it != num_count.end(); it++){ if(frequent_heap.size() < k){ frequent_heap.push(*it); } else if(it->second >= frequent_heap.top().second){ frequent_heap.pop(); frequent_heap.push(*it); } }
咱們都知道 c++ 和 python 是不一樣的語言,它們有着顯著的區別,不過一不當心咱們就會忘記它們之間的差異,從而寫出bug來。不信?來看 69 Sqrt(x),實現 int sqrt(int x)
。這題目是經典的二分查找(固然也能夠用更高級的牛頓迭代法),用 python 來實現的話很容易寫出 AC 的代碼。
若是用 C++ 的話,相信不少人也能避開求中間值的整型溢出的坑:int mid = low + (high - low) / 2;
,因而寫出下面的代碼:
int low = 0, high = x; while(low <= high){ // int mid = (low+high) / 2, may overflow. int mid = low + (high - low) / 2; if(x>=mid*mid && x<(mid+1)*(mid+1)) return mid; else if(x < mid*mid) high = mid - 1; else low = mid + 1; }
很惋惜,這樣的代碼仍然存在整型溢出的問題,由於mid*mid 有可能大於 INT_MAX
,正確的代碼在這裏。當咱們被 python 的自動整型轉換寵壞後,就很容易忘記c++整型溢出的問題。
除了臭名昭著的整型溢出問題,c++ 和 python 在位運算上也有着一點不一樣。以 371 Sum of Two Integers 爲例,不用 +, - 實現 int 型的加法 int getSum(int a, int b)
。其實就是模擬計算機內部加法的實現,很明顯是一個位運算的問題,c++實現起來比較簡單,以下:
int getSum(int a, int b) { if(b==0){ return a; } return getSum(a^b, (a&b)<<1); }
然而用 python 的話,狀況變的複雜了不少,歸根到底仍是由於 python 整型的實現機制,具體代碼在這裏。
若是說 LeetCode 上面的題目是一塊塊金子的話,那麼評論區就是一個點綴着鑽石的礦山。多少次,當你絞盡腦汁終於 AC,興致勃發地來到評論區準備吹水。結果迎接你的倒是大師級的代碼。因而,你高呼:尼瑪,居然能夠這樣!而後閉關去思考那些優秀的代碼,順便默默鄙視本身。
除了優秀的代碼,有時候還會有直觀的解題思路分享,方便看看別人是如何解決這個問題的。@MissMary在「兩個排序數組中找出中位數」這個題目中,給出了一個很棒的解釋:Share my o(log(min(m,n)) solution with explanation,得到了400多個贊。
你也能夠評論大牛的代碼,或者提出改進方案,不過有時候可能並不是如你預期同樣改進後代碼會運行地更好。在 51. N-Queens 的討論 Accepted 4ms c++ solution use backtracking and bitmask, easy understand 中,@binz 在討論區中納悶本身將數組 vector<int> (取值非零即一)改成 vector<bool> 後,運行時間變慢。@prime_tang 隨後就給出建議說最好不要用 vector<bool>,並給出了兩個 StackOverflow 答案。
當你逛討論區久了,你可能會有那麼一兩個偶像,好比@StefanPochmann。他的一個粉絲 @agave 曾經問 StefanPochmann 一個問題:
Hi Stefan, I noticed that you use a lot of Python tricks in your solutions, like "v += val," and so on... Could you share where you found them, or how your learned about them, and maybe where we can find more of that? Thanks!
StefanPochmann 也不厭其煩地給出了本身的答案:
@agave From many places, though I'd say I learned a lot on CheckiO and StackOverflow (when I was very active there for a month). You might also find some by googling python code golf.
原來大神也是在 StackOverflow 上修煉的,看來須要在 爲何離不開 StackOverflow 中添加一個理由了:由於 StefanPochmann 都混跡於此。
相似這樣友好,充滿技術味道的討論,在 LeetCode 討論區遍地都是,絕對值得咱們去好好探訪。
偶爾會聽旁邊人說 XX 大牛 LeetCode 刷了3遍,成功進微軟,還拿了 special offer!聽起來好像刷題就能夠解決工做問題,不過要知道還有刷5遍 LeetCode 仍然沒有找到工做的人呢。因此,不要想着刷了不少遍就能夠找到好工做,畢竟比你刷的還瘋狂的大有人在(開個玩笑)。
不過,想一想前面列出的那些好處,應該值得你們抽出點時間來刷刷題了吧。
跟波利亞學解題
爲何我反對純算法面試題
聊聊刷題
如何看待中國學生爲了進 Google、微軟等企業瘋狂地刷題?
LeetCode 編程訓練
國內有哪些好的刷題網站?
本文由 selfboot 發表於 我的博客,採用署名-非商業性使用-禁止演繹 3.0進行許可。
非商業轉載請註明做者及出處。商業轉載請聯繫做者本人。
本文標題爲: LeetCode 刷題指南(一):爲何要刷題
本文連接爲: http://selfboot.cn/2016/07/24...