LeetCode 1000+ 感想,讲讲我眼中的刷题,分享刷题方法

录一下刷LeetCode1000+的感想。LeetCode代码仓库

刷题,从2015年就开始了。那年我大一,跟着两位厉害的室友参加ACM培训,从东大OJ开始,后来去杭电OJ,再后来去POJ。在ACM OJ上刷题不多,大概几百道(后来因为专注于数学建模,放弃了ACM。)。2016年,在同学甲的推荐下,我接触了LeetCode,“找工作都看这个”,但是当时对刷题的热情已经淡了下来,很少刷。闲着没事的时候会去看两眼,可能做一道两道。刷题成了一种消遣,一种维持手热的工具。毕竟长时间不写代码,#include <stdio.h> 都写的费劲。

大概是在大一下,或者大二上,在同学乙的带动下,又刷了一些计蒜客,Kick Start等,感受一下不同的平台。但是唯有LeetCode算是一直坚持吧。后来大三大四了,又增加了刷题的频率,到了今天,猛然发现,已经超[抄]过了1000题。

虽然刷题的时间跨度实在是长,但是大部分题都是最近这两年刷的。也总结了一套刷题的方法,希望能给他人带来一些启迪。


#如何刷题

有些朋友可能注意到了,我上面用了“[抄]”。是的,我建议你抄代码。LeetCode与ACM相比,难度低一些。如果你想锻炼自己的思维,并且不想抄,我首先建议你刷ACM,同时刷个几年题,这样效果好。我在同学群里见到的,同学们刷题都是为了面试,大多数也都是临时抱佛脚,没有很多时间,那你先抄一遍,熟悉熟悉LeetCode的套路,这样是最好的。当然了,抄,也不是不动脑子的复制粘贴,先理解题意,然后自己思考,给自己一个时限,三分钟没有基本思路,十分钟没有完善思路,就开始抄。要一行一行的自己手打,尽力理解他人的代码。这里我推荐一位前辈,花花酱,他写的代码非常非常漂亮。即使是抄代码,也要养成一个好的代码风格。

 

“熟读唐诗三百首,不会吟诗也会吟。”[1]

 

LeetCode上有标记好的类型,每种类型,集中抄一遍,做到知道常规思路,熟记解题模板。在这个过程中,你会理解这种类型的题,知道LeetCode的套路。DP, Greedy, Tree等,都是规律性较强的类型(我默认你学过了数据结构与算法,但其实刷LeetCode也可以是纯小白),直接抄代码的效果会很好。你会发现,抄着抄着,自己就能独立做出这个类型的题目了,至少有较为完善的思路,可能只有一点细节不够。

第一遍可以抄,过一星期左右,需要你第二遍刷这道题。这个时候就要你独立思考了,看看半个小时内能不能解决。如果能做出来,那么,偶尔刷一遍这道题,增强自己对这种类型题目的记忆与理解,同时看看有没有其他的思路。如果第二遍刷题的时候你依旧不会这道题,那你要复习自己抄的代码,研究明白每一行代码的原理。这样,你第三次刷的时候,很难不会了吧。第三遍刷,可以乱序,第一遍刷,建议按照分类刷。

同时,要善于使用LeetCode的社区。LeetCode最突出的优点,是其社区。大多数题都有不止一种解法,你可能会解一道题,也可能是时间复杂度,空间复杂度最优的解法,但这不代表其他解法没有价值,多看看别人的思路,拓展自己的“工具箱”,这样能更好的面对实际问题。很多时候,你都会被别人的巧妙解法而吸引,最终发现刷题的乐趣,沉迷刷题无法自拔。


#做LeetCode题的步骤

首先理解题意,看看是哪种类型的题目。LeetCode给每到题都分好类了,但是我不建议大家每次刷新题时都看官方分类。通常来讲解题方法很好区分。

树和图的题,因为其天然的数据结构,递归与迭代应该是你主要的思考方向。看到树的题,首先应该想树的几种遍历方法,图的题,就是常见的图的考法,最短路径啊,连通性什么的。

给的输入是数组的话,主要考虑DP与Greedy。DP就要找到状态转移方程,而贪心都是要有排序的,你要排序了才能贪。比较好区分。多维数组,更可能是DFS,BFS,出题的类型也比较明显,做几道就明白了。

string有时候可以按照数组去看,有时候是匹配啊,回文啊,有时候是考一些方法不是很明确的题,需要你动脑。

其他少见的方法,如数学法,暴力破解,模拟过程等,要看情况了。

另一个值得注意的地方就是输入的数据规模。LeetCode不太可能给你一个100大小的数组,然后可以在$\mathcal{O}(1)$的时间内求解(的确有这样的题目)。一般情况都是$10^5, 10^6$这样的最终大小。如果一个题的输入是$10^9$规模的,那么这道题时间复杂度最高是$\mathcal{O}(n)$,可能是常数时间或者$\mathcal{O}(\log{n})$


#刷题的境界

我认为,刷题与读书是类似的事。借用王国维对读书的三种境界划分:

 

“昨夜西风凋碧树,独上高楼,望尽天涯路”[2]

“衣带渐宽终不悔,为伊消得人憔悴”[3]

“众里寻他千百度,蓦然回首,那人却在灯火阑珊处”[4]

 

首先要多刷,广刷,看很多很多题,这就是“望尽天涯路”。许多题目,都来源与实际生产中的问题。生产中不会遇到的问题,很少在LeetCode中出现。其次就是思考,刷了那么多题,抄了那么多题,你总会有自己的理解,思考。这就是“为伊消得人憔悴”。第三重境界我还不理解,先放在这里,等着理解了再回首。


#趣事与小技巧

我写代码的时候,会写错i与1,o与0。很多用到nums[i]的循环,我都会写成nums[1],很多初始化为0的整型变量,我会写成o,int a = o;。就在刚刚写博客时,那个nums[i]写成了nums[1]……。次数之多,我自己都非常困惑。我这个时候真没有抄代码啊,怎么还写错这个了呢。后来研究了很长时间,发现,是手指敲键盘的速度跟不上我的思维。很多代码,我在思考的时候就写在脑海了,打字的时候,就是抄脑海里的影像,i与1太像了,导致写错。

对于打表这个方法呢,人们评价不一。我个人推荐打表。我们写代码的目的是解决问题。打表法,计算量小,速度高,鲁棒性好,bug少,维护也方便,为什么不用呢?当情况不是很多且已经确定的时候,打表就是最好的方法。一些关键部分,厂家都会打表,烧录到硬件上。不要羞于打表,不要认为打表与brute force是笨的象征。


#为什么不多多记录LeetCode

在Quora看到有人提问为什么不要过多展示自己刷LeetCode。回答,你展示太多LeetCode,会给面试官一种,你在针对面试而训练,而不是更多的参与实际开发,这样的感觉。因此他们都建议,偷偷的刷题,不要展示。有道理。但是对于我这种以刷题为消遣,刷了很多年的人来说,刷题多很正常啊。反而是参与的项目,不是与翻墙有关不敢用自己的大号写,就是属于私有仓库,不能公开,因此反而没有很多项目经历。真难啊。


  1. 蘅塘退士 ↩︎

  2. 晏殊 《蝶恋花》 ↩︎

  3. 柳永 《蝶恋花》 ↩︎

  4. 辛弃疾 《青玉案》 ↩︎