练习之前,你需要了解的¶
统计信息:字数 8422 阅读17分钟
资料链接:http://47.98.159.95/leetcode-js/
文档还在更新中;优先看书;这个作者水平一般
适用人群¶
如果你正在为面试做准备,却对于庞杂的数据结构和算法知识,不知道何从下手;
如果你以前曾经学过一些基础的数据结构或者算法的基础知识,却根本没有理解清楚,更不能独立完成算法的设计, 想重新巩固这方面的知识;
如果已经有了一小段工作经验,但却老是在整天用轮子、调API当中度过,想看一看底层的源码却常常因编程的内力不足而放弃;
如果你听说过LeetCode这个网站,想要一刷到底,迈向算法巅峰,却因为浩瀚的题量和缺乏系统训练感到无力,三天打鱼两天晒网,进而感到焦虑,甚至放弃......
如果上面任何一条符合你的现状,那么恭喜你,你来对了地方。作为一个圈内小有名气的前端博主,我想借着我的影响力,分享出我系统梳理和练习的过程,希望能够帮助到更多跟我一样遇到类似困难的人,让你少一些不必要的折腾。
全程使用的语言是 JavaScript,因此标题上说的是前端xxx, 但实际上你也知道,数据结构和算法这东西主要是考验一个人的思维,至于语言,其中并没有用到任何 JS 的高深语法特性,只要有所编程经验,能理解代码是完全没有问题的,这一点大家放心。
算法没有用?¶
我想各位当中肯定有准备面试的同学,那么你肯定听说过面试造火箭,工作拧螺丝, 不少人也拿这句话拿来诟病当前一些互联网大厂的算法面试,因此就有这样的言论: 除了应付面试,学算法其实没啥用。
这句话我并不完全反对,因为现在随着技术生态的发展,各位走在领域前沿的大牛们已经给大家备足了轮子,遇到一般的业务问题直接把人家的方案拿到用就可以了,另外我也看到过一句话,刚开始觉得扯淡,后来想想觉得有那么一丝道理:
凡是需要跨过一定智商门槛才能掌握的技术,都不会轻易的流行。
换句话说:技术变得更简单,别人更愿意用,更容易流行。
这也是当前各种技术框架的真实写照: 足够好用,足够简单,简单到你不需要知道底层复杂的细节。
那么问题来了,作为一个集智慧和才华于一身的程序员,自己的价值在哪里?
我觉得价值的大小取决于你能够解决的问题,如果说照着设计稿画出一个简单的 Button,你能完成,别的前端也能完成,甚至后后端的同学都能把效果差不多做出来,那这个时候就谈何个人价值?只不过在一个随时可替代的岗位上完成了大多数人能轻易做到的事情,张三来完成,或者李四来完成,其实没什么区别。
但是现在如果面对的是一个复杂的工程问题,需要你来开发一个辅助业务的脚手架工具,改造框架源码来提高项目的扩展性,或者面对严重的性能问题能马上分析出原因,然后给出解决的思路并在不同因素中平衡,这些都不是一个业余的玩家能够在短时间内胜任的,这就是体现自己价值的地方。
回到算法本身,它代表的是你解决更加复杂问题能力的一部分。
可能干讲不容易理解,我们以 Vue 这个框架为例,如果你以前没有接触过深度优先遍历和递归的概念,没有看过相应的代码,那么虚拟 DOM 整个patch的源码你是基本不可能看懂的;如果你没有系统掌握过栈先进后出这种特点的应用,你也是很难理解 Vue 模板编译阶段为什么要用栈来检查标签是否正常闭合;同样的,如果你没有回溯这种算法的代码经验,你也是很难理解 Vue 模板编译的优化阶段,到底是怎样在从父到子深度优先遍历的过程中检查到非静态的子节点后给父节点打上标记;并且,如果你以前不知道 LRU 缓存淘汰算法究竟是个什么东西,你看到keep-alive组件的实现这里会非常纳闷:
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
}
缓存命中了,为什么还要维护一个 keys 数组,而且把这个 key 从数组中删了,又要放到末尾,啥操作呢?
如果之前有相应算法基础,你反而会觉得这个理所当然的事情。
看到了吧?你觉得尤大能写出优秀的“明星”项目,会没有一点扎实的数据结构和算法功底?
当然不仅仅是前端领域,我想服务端也是差不多的情况,这里就不多举例了。
所以各位,我认为基本的算法能力对于一个想要解决复杂问题的工程师而言,不是一个加分项,而且是必备项。算法有大用。
如何系统练习?¶
接下来我来分享一下练习数据结构和算法的一些心得,或者方法,分两个关键字来说: 系统和练习。
专题突破¶
如何做到系统的训练?
我想按照LeetCode上的顺序一题一题刷肯定不是系统,大部分情况是相邻的几个题毫无联系,这一题做了个链表相关的,下一题又是一个哈希表,再下一题又了一个二分搜索树,思维不断的跳转,一方面可能你的基础很薄弱,各个数据结构和算法理解的并不深刻,反复跳到你不熟悉的地方,会挫败你的信心,增加焦虑,甚至直接劝退,另一方面,可能再遇到难一点的链表题,你又不会了,可能你会纳闷: 不是刚刚才写出来了一个链表题吗?怎么现在又不会了?容易让你产生自我怀疑,也会影响你训练的自驱力。
因此我觉得分专题各个训练突破是一个相对合理的战略。在做到专和精的同时,也会让你掌握的更快,从而更容易解出类似的题,增强自信心。
另外要介绍的就是本系列的专题系统了,一共会分为这些模块:
专题目录
链表篇、栈和队列篇、哈希表篇、二叉树篇、字典树、并查集、常见排序和查找算法、回溯算法、动态规划、贪心算法、LRU和LFU、字符串和正则篇
一共十二个模块,相信你会有不错的收获。
刻意练习¶
《异类:不一样的成功启示录》这本书里面谈到一万小时刻意练习成为高手的理论,后面也很多人谈到这个理论,我几年前就听说过,但真正体会到它的实际意义却是最近在训练算法的过程中,而且是走了很多弯路之后。可见理论到实际的过程是多么艰难。
我所理解的刻意练习,运用到算法的训练上面来,就是两点:
- 经常性地做你不会做的题
- 多种解法方式轮流试,最大化地挖掘一道题的价值
刻意练习有一个重要的观点是走出舒适圈。对于算法练习也是一样,为什么很多人觉得刷算法没用,那是因为他们总是在做自己熟悉的题型,用自己熟悉的方法,蹲在自己的舒适圈,这样做的再多也意义不大,但是如果总是在做自己不熟练的题型,用不一样的方法,对于自己思维的成长是相当有帮助的,所以我觉得把握经常性地做不会的题,这样做的越多越好,当然了,时间有限,我们需要根据自己的需要来取舍。
之所以我会把这个系列叫做练习,而不是刷题,就是因为练习的本质是练,而不是简单的AC就可以。
可能有经验的同学,用递归的方式很容易就解出来了。但是,你有想过如何用非递归的方式吗?如果能用非递归来是实现一遍,相信给自己带来的帮助和提升会比递归解大得多。
另外说一句,本系列中所有的代码都是原创的,而且不仅仅给出递归代码,在绝大多数情况下会给出对应的非递归解法,深入地挖掘一道题的最大价值,达到练习而不是刷题的效果。
最后的最后,我要强调的是: 对于这种修炼内功的练习,任何视频或者专栏都仅仅只是辅助作用,最重要的是还是自己的坚持和独立思考,如果你通过我这个系列能够让自己的算法能力更上一层楼,或者说能够有所收获,你应该感谢的是你自己。