最近忽然想起自己很久没有刷过算法题了,在 Leetcode 上捡了几题刷了一下,发现手确实生了,看到了 labuladong 大佬对数据结构和算法的理解比较深入,故做此记录。

如果要让我⼀句话总结,我想说算法的本质就是「穷举」

这么说肯定有人要反驳了,真的所有算法问题的本质都是穷举吗?没有例外吗?

例外肯定是有的,比如前几天我还发了 一行代码就能解决的算法题,这些题目类似脑筋急转弯,都是通过观察,发现规律,然后找到最优解法,不过这类算法问题较少,不必特别纠结。再比如,密码学算法、机器学习算法,它们的本质确实不是穷举,而是数学原理的编程实现,所以这类算法的本质是数学,不在我们所探讨的「数据结构和算法」的范畴之内。

顺便强调下,「算法工程师」做的这个「算法」,和「数据结构与算法」中的这个「算法」完全是两码事, 免得⼀些初学同学误解。

对前者来说,重点在数学建模和调参经验,计算机真就只是拿来做计算的工具而已;而后者的重点是计算机思维,需要你能够站在计算机的视角,抽象、化简实际问题,然后用合理的数据结构去解决问题。

所以,你千万别以为学好了数据结构和算法就能去做算法工程师了,也不要以为只要不做算法工程师就不需要学习数据结构和算法。坦白说,大部分开发岗位工作中都是基于现成的开发框架做事,不怎么会碰到底层数据结构和算法相关的问题,但另⼀个事实是,只要你想找技术相关的岗位,数据结构和算法的考察是绕不开的,因为这块知识点是公认的程序员基本功。

为了区分,不妨称算法工程师研究的算法为「数学算法」,称刷题面试的算法为「计算机算法」,我写的内容主要聚焦的是「计算机算法」

这样解释应该很清楚了吧,我猜大部分人的目标是通过算法笔试,找⼀份开发岗位的工作,所以你真的不需要有多少数学基础,只要学会⽤计算机思维解决问题就够了。

其实计算机思维也没什么高端的,你想想计算机的特点是啥?不就是快嘛,你的脑回路⼀秒只能转⼀圈,人家 CPU 转几万圈无压力。所以计算机解决问题的方式大道至简,就是穷举。

我记得自己刚入门的时候,也觉得计算机算法是⼀个很高大上的东西,每见到⼀道题,就想着能不能推导出 ⼀个什么数学公式,啪的⼀下就能把答案算出来。

比如你和一个没学过计算机算法的人说你写了个计算排列组合的算法,他大概以为你发明了一个公式,可以直接算出所有排列组合。但实际上呢?没什么高大上的公式,后文 回溯算法秒杀排列组合子集问题 写了,其实就是把排列组合问题抽象成一棵多叉树结构,然后用回溯算法去暴力穷举。

对计算机算法的误解也许是以前学数学留下的「后遗症」,数学题⼀般都是你仔细观察,找几何关系,列方程,然后算出答案。如果说你需要进行大规模穷举来寻找答案,那大概率是你的解题思路出问题了。

而计算机解决问题的思维恰恰相反:有没有什么数学公式就交给你们人类去推导吧,如果能找到⼀些巧妙的 定理那最好,但如果找不到,那就穷举呗,反正只要复杂度允许,没有什么答案是穷举不出来的,理论上讲 只要不断随机打乱⼀个数组,总有⼀天能得到有序的结果呢!当然,这绝不是⼀个好算法,因为鬼知道它要运行多久才有结果。

技术岗笔试面试考的那些算法题,求个最大值最小值什么的,你怎么求?必须得把所有可行解穷举出来才能找到最值对吧,说白了不就这么点事儿么。

但是,你千万不要觉得穷举这个事儿很简单,穷举有两个关键难点:无遗漏、无冗余

遗漏,会直接导致答案出错;冗余,会拖慢算法的运⾏速度。所以,当你看到⼀道算法题,可以从这两个维度去思考:

  1. 如何穷举?即⽆遗漏地穷举所有可能解。
  2. 如何聪明地穷举?即避免所有冗余的计算,消耗尽可能少的资源求出答案。

不同类型的题目,难点是不同的,有的题目难在「如何穷举」,有的题目难在「如何聪明地穷举」。

什么算法的难点在「如何穷举」呢?一般是递归类问题,最典型的就是动态规划系列问题

后文 动态规划核心套路 阐述了动态规划系列问题的核心原理,无非就是先写出暴力穷举解法(状态转移方程),加个备忘录就成自顶向下的递归解法了,再改一改就成自底向上的递推迭代解法了, 动态规划的降维打击 里也讲过如何分析优化动态规划算法的空间复杂度。

上述过程就是在不断优化算法的时间、空间复杂度,也就是所谓「如何聪明地穷举」。这些技巧一听就会了,但很多读者留言说明白了这些原理,遇到动态规划题目还是不会做,因为想不出状态转移方程,第一步的暴力解法都写不出来。

这很正常,因为动态规划类型的题目可以千奇百怪,找状态转移方程才是难点,所以才有了 动态规划设计方法:数学归纳法 这篇文章,告诉你递归穷举的核心是数学归纳法,明确函数的定义,然后利用这个定义写递归函数,就可以穷举出所有可行解。

什么算法的难点在「如何聪明地穷举」呢?一些耳熟能详的非递归算法技巧,都可以归在这一类

比如后文 Union Find 并查集算法详解 告诉你一种高效计算连通分量的技巧,理论上说,想判断两个节点是否连通,我用 DFS/BFS 暴力搜索(穷举)肯定可以做到,但人家 Union Find 算法硬是用数组模拟树结构,给你把连通性相关的操作复杂度给干到 O(1) 了。

这就属于聪明地穷举,大佬们把这些技巧发明出来,你学过就会用,没学过恐怕很难想出这种思路。

再比如贪心算法技巧,后文 当老司机学会贪心算法 就告诉你,所谓贪心算法就是在题目中发现一些规律(专业点叫贪心选择性质),使得你不用完整穷举所有解就可以得出答案。

人家动态规划好歹是无冗余地穷举所有解,然后找一个最值,你贪心算法可好,都不用穷举所有解就可以找到答案,所以后文 贪心算法解决跳跃游戏 中贪心算法的效率比动态规划还高。

再比如大名鼎鼎的 KMP 算法,你写个字符串暴力匹配算法很容易,但你发明个 KMP 算法试试?KMP 算法的本质是聪明地缓存并复用一些信息,减少了冗余计算,后文 KMP 字符匹配算法 就是使用状态机的思路实现的 KMP 算法。

本文转载至:labuladong 的算法小抄

文章作者: 听到微笑
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 听到微笑的博客
数据结构与算法 数据结构与算法
喜欢就支持一下吧