上周在公司内部分享了自己练习算法的心得和经验,有小伙伴表示分享的内容给他带来了价值,也很具备参考意义,于是就算法写成文章分享出来,近几个月来,自己每周都会花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 持续反馈(主动学习)
- 重在实践:三分学,七分练(切莫贪多,贵在坚持)
- 组队打卡:找到有共同兴趣的朋友一起练习,更容易坚持(想要加入算法打卡群的请给我留言)