纵横算法之三:算法到底考什么

Posted 纵横千里,捭阖四方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了纵横算法之三:算法到底考什么相关的知识,希望对你有一定的参考价值。

关于算法有太多的疑问和截然相反的争论,例如经常看到很多人说算法是玄学,是聪明者的游戏,学不好是自己笨,数学不好所以算法不会等等。还有人会觉得工作不用所以不学,算法不用学,在工作中多积累积累就行了吗?还有人总是感觉各种算法课程没有用,这都是为什么呢?

我们继续谈。

我们前面说面试以数据结构及其变形为主,这能占到现场手写算法的80%,那这些内容到底有哪些呢?高级和初级算法怎么区分呢?

我们先回忆一下数据结构都有哪些内容。如果你学过数据结构课程,你应该知道大致有那么几个章节:数组(包括一维和二维)、链表(包括单链表、双链表、循环链表)、队列、栈、Hash与集合、树(包括二叉树、AVL树、N叉树、堆、红黑树、哈夫曼树、B*树等等)、图以及数据结构的两个基本问题 查找(主要是二分查找)和排序(若干常见排序策略)。此外还有三个不是数据结构,但是结构特殊一般需要我们特殊对待的结构字符串、位图和数字。

现在我们来看,这些问题哪些是工作中经常用的,或者说是怎么用的。

1.对于数组,算法里常用int []array和int [][]array来表示一维和二维数组,而在算法里我们更多是采用List<Object>和List<List<Object>>这种方式来表示一维和二维数组结构,例如处理Excel等等就会这么做。为什么不一样呢?因为前一种方式更简洁,而且与语言无关。而后者则可以充分利用List等类的基础接口提高我们的开发效率和程序的稳定性。

2.再看链表,我的映像中极少会使用java写链表的代码,更多是采用组合和List等方式解决实际问题,也许只有在责任链、迭代结构中偶尔会有一点链表的影子,但是在工程里极少这么写,问题就在后期可能难以维护,出现问题也不好解决,简单来说就是中看不中用。

3.栈和队列、Hash与集合这几个绝对是应用开发的重点,我们会大量使用这几种工具。例如阻塞队列、HashMap等等。如果你研究JUC的源码,你会发现整个JUC是以AQS为基础,增加若干链表和辅助队列构建起来的,例如重入锁、Condition等等、而AQS本身也是一个双向循环队列。所以队列、Hash更多是在技术面试的过程中直接问你内部原理。

 那会不会手写呢?很遗憾,几乎没有单纯考队列的问题,整个LeetCode里也没几道专门考察队列的问题。而Hash更多是算法的备胎,只要有更费脑细胞的算法,面试官就不会让你用Hash。为啥呢?一方面是因为很多题目一旦使用Hash就毫无含金量了,这就起不到考察思维能力的要求。另外就是本身需要开辟O(n)的空间,与那些中间只有O(1)相比就没有优势了。而在工作中,我们当然会选择简单、稳定、高效、易于维护的方式,大不了增加一下内存空间大小就行。这就看到了工程应用和面试算法的侧重点是不一样的。

对于栈,本身是有一些典型的题目的,例如括号匹配、计算器、表达式计算(例如逆波兰表达式)等等,但是请问工作中你遇到过几次这种问题?真遇到我们也会使用common、guava等库来辅助实现,不会自己亲自写。

4.再看树,请问你会在工程里写一个二叉树吗?真有一对多的关系,还是使用List多,因为二叉树扩展性太差了。而B*树、红黑树这些则是mysql索引、HashMap源码等涉及的问题,本身还是挺复杂的,还是不会手写,算法真正喜欢考你的是二叉树的层次遍历、前序和后序遍历、中序遍历以及搜索树这几种场景。

5.图呢?几乎不会用,如果谁敢将结构设计成这样,十有八九是有问题的,因为你的代码用起来会有巨大的隐患。而在算法也很少考图,主要是因为构造图要么用邻接矩阵,要么用邻接表,代码都很多,再写算法就显得又臭又长,而图的广度优先、深度优先问题在二叉树遍历都有很好的体现。

6.查找和排序呢?遇到这种场景,我们一般也是直接调用Arrays或者List里的库函数,而不会自己写,为啥,世界高手已经将其维护几十年的算法绝对比你拍脑袋写出来的好,为了线上服务的稳定性,我们绝对倾向使用人家的。而在面试的时候呢?你就要硬着头皮写二分、写归并排序、写快速排序。

7.字符串的重要性不用说,工作中经常遇到,我们一般会使用很多工具库来减轻开发压力。位图的好处是占用空间小,计算效率高,所以在jvm、JUC、Spring、Dubbo等源码中大量使用,这个是我们研究源码一个非常重要的问题。而数字相对来说比较少一些。

从上面我们可以看到工作和面试对算法的要求是完全不一样的:

工作和面试对算法的要求对比
工作中面试中
基本要求稳定、高效、易维护思维含量、执行效率如何
数组使用Listint[],int[][]
链表 几乎没有考察大热门
队列天天用,大量用不会直接考,更多在广度优先等场景涉及
需要时使用库函数没实际用处的问题:括号匹配、表达式等
Hash天天用,能用就用永远的备胎,能不用就不用
二叉树几乎没有面试属第一,永远炸子鸡
红黑树、B+树等Mysql、HashMap等的根基理解原理就行,一般不用写
不会用极少遇到
查找、排序调库函数自己写

从图中我们可以看到,算法的重点是 数组、链表、二叉树、查找排序这些问题。这说明什么?说明如果你想靠工作经验来积累算法基本不可能,除非你技术足够好,面试官不考你算法,但是算法让然是不行的。

那链表等到底考什么东西,该怎么准备,脉络是什么呢?我们下一篇《算法的学习脉络》单独讨论。这里先看另外一个问题,数据结构和算法,以及初级算法和看似玄学的高级算法是什么关系呢?

对于数据结构和算法的关系,仁者见仁智者见智,我的理解是数据结构是载体,而算法是解决某类问题的思想。不管多么复杂的算法,最终都要落地到数组、二叉树等这种基本的结构上。而算法则会根据很多具体的问题抽象出一些思想,例如双指针、递归、分治、回溯、迭代、贪心、动态规划等等。

对于初级算法和高级算法的关系,同样仁者见仁智者见智,我的理解是基于数据结构的变形和拓展就属于初级算法。更难,针对场景更特殊的问题都属于高级算法,例如滑动窗口、回溯、贪心、动态规划等等,很明显后者是前者的延伸,前者是后者的基础。具体看这个表:

初级算法与高级算法的关系
初级算法重要问题问题高级算法
一维数组增删时减少频繁移动,采取双指针策略滑动窗口问题
二维数组常见的花样就十来道题,会了就行了高级算法的载体
链表常见的花样就那么多,会了就行了没有了
队栈Hash常见的花样就那么多,会了就行了单调栈、单调队列
常见的花样就那么多,会了就行了前缀树、并查集
递归有些问题用递归也写不出来回溯
使用滚动数组等优化递归动态规划的一部分
一些特殊的问题贪心
数学与数字等等数学问题等等

从上面这个简单的图可以看到两者有紧密的关系,回溯、动态规划、贪心、数学等等有大量的算法题,例如背包、子序列而研究这些问题的前提都是你对基础算法足够清楚。否则就会简单的不会,难的学不清楚,也就是简单的问题搞不清楚,就没资格学高级算法。在金庸武侠中,经常见到这样的场景,为什么同样的功夫有的越学越牛,有的就会走火入魔,甚至功力尽失,就是因为基础不牢,然后地动山摇。

那怎么才算把基础清楚了,什么情况下学习高级算法才有效果呢?初级和高级的边界在哪里?我找了几个题目,如果一题25分,看看你能得多少,至少75分才算合格吧。

以上是关于纵横算法之三:算法到底考什么的主要内容,如果未能解决你的问题,请参考以下文章

纵横算法之一:面试为什么非要有算法

纵横算法之四:算法应该怎么学

纵横算法之二:社招如何面对算法面试

纵横算法之五:想学算法,时间不够怎么办

优化求解基于 Sobol 序列和纵横交叉策略的麻雀搜索算法(SSASC) Matlab源码

优化求解基于 Sobol 序列和纵横交叉策略的麻雀搜索算法(SSASC) Matlab源码