纵横算法之三:算法到底考什么
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等源码中大量使用,这个是我们研究源码一个非常重要的问题。而数字相对来说比较少一些。
从上面我们可以看到工作和面试对算法的要求是完全不一样的:
工作中 | 面试中 | |
基本要求 | 稳定、高效、易维护 | 思维含量、执行效率如何 |
数组 | 使用List | int[],int[][] |
链表 | 几乎没有 | 考察大热门 |
队列 | 天天用,大量用 | 不会直接考,更多在广度优先等场景涉及 |
栈 | 需要时使用库函数 | 没实际用处的问题:括号匹配、表达式等 |
Hash | 天天用,能用就用 | 永远的备胎,能不用就不用 |
二叉树 | 几乎没有 | 面试属第一,永远炸子鸡 |
红黑树、B+树等 | Mysql、HashMap等的根基 | 理解原理就行,一般不用写 |
图 | 不会用 | 极少遇到 |
查找、排序 | 调库函数 | 自己写 |
从图中我们可以看到,算法的重点是 数组、链表、二叉树、查找排序这些问题。这说明什么?说明如果你想靠工作经验来积累算法基本不可能,除非你技术足够好,面试官不考你算法,但是算法让然是不行的。
那链表等到底考什么东西,该怎么准备,脉络是什么呢?我们下一篇《算法的学习脉络》单独讨论。这里先看另外一个问题,数据结构和算法,以及初级算法和看似玄学的高级算法是什么关系呢?
对于数据结构和算法的关系,仁者见仁智者见智,我的理解是数据结构是载体,而算法是解决某类问题的思想。不管多么复杂的算法,最终都要落地到数组、二叉树等这种基本的结构上。而算法则会根据很多具体的问题抽象出一些思想,例如双指针、递归、分治、回溯、迭代、贪心、动态规划等等。
对于初级算法和高级算法的关系,同样仁者见仁智者见智,我的理解是基于数据结构的变形和拓展就属于初级算法。更难,针对场景更特殊的问题都属于高级算法,例如滑动窗口、回溯、贪心、动态规划等等,很明显后者是前者的延伸,前者是后者的基础。具体看这个表:
初级算法 | 重要问题问题 | 高级算法 |
一维数组 | 增删时减少频繁移动,采取双指针策略 | 滑动窗口问题 |
二维数组 | 常见的花样就十来道题,会了就行了 | 高级算法的载体 |
链表 | 常见的花样就那么多,会了就行了 | 没有了 |
队栈Hash | 常见的花样就那么多,会了就行了 | 单调栈、单调队列 |
树 | 常见的花样就那么多,会了就行了 | 前缀树、并查集 |
递归 | 有些问题用递归也写不出来 | 回溯 |
使用滚动数组等优化递归 | 动态规划的一部分 | |
一些特殊的问题 | 贪心 | |
数学与数字等等 | 数学问题等等 |
从上面这个简单的图可以看到两者有紧密的关系,回溯、动态规划、贪心、数学等等有大量的算法题,例如背包、子序列而研究这些问题的前提都是你对基础算法足够清楚。否则就会简单的不会,难的学不清楚,也就是简单的问题搞不清楚,就没资格学高级算法。在金庸武侠中,经常见到这样的场景,为什么同样的功夫有的越学越牛,有的就会走火入魔,甚至功力尽失,就是因为基础不牢,然后地动山摇。
那怎么才算把基础清楚了,什么情况下学习高级算法才有效果呢?初级和高级的边界在哪里?我找了几个题目,如果一题25分,看看你能得多少,至少75分才算合格吧。
以上是关于纵横算法之三:算法到底考什么的主要内容,如果未能解决你的问题,请参考以下文章