如何学习数据结构和算法


原文地址
上图是2015年的我。我以“创始员工”的身份加入了一家初创公司,当时我们从一家公司获得了50万美元的种子期融资,但6个月后这家公司倒闭了,于是我们正处于寻找新的职位。通过一位创业公司创始人的介绍,我获得了Codecademy的面试机会。

在与Codecademy的电话中,他们说:“别担心,不会问疯狂的算法问题或类似的问题。于是我认为这意味着我根本不需要学习算法。

在现场面试中,我遇到了两轮算法问题,事后看来都是非常基础的算法题。我记得其中一个是问我如何在网格中从A点穿越到B点。我完全不知道该怎么做,所以我只能瞎蒙了。我聚焦在一个无限的while循环上,我在黑板上写了:

“ while (true) {…”

在循环中,这个点在每次迭代中都会改变方向,这取决于它是否碰到了墙,如果找到了目标,它最终会退出while循环。面试官肯定是一副不屑的样子,但他保持冷静,继续接受我的不同想法。

那次惨败让我看清了面试需要回答的问题类型。两个半月后,我通过了谷歌、Uber、Shutterstock和Rent the Runway的电话面试。我参加了Shutterstock、Rent the runway和Uber的现场面试,都通过了。谷歌重新安排了我的面试时间,比我最后告诉他们的时间晚了两周。到那时,我已经收到了三份工作offer,我对Uber非常感兴趣,于是我加入了Uber。

我在短短几个月内从0到100,除了持续不断地学习,我没有做任何特别的事情。这就是为什么我坚信任何工程师都可以很好地解决这些数据结构& 算法问题,并进入F.A.A.N.G.(国外大厂)或类似的高薪职位。



一开始,我觉得我真的不够格,因为我必须努力学习才能通过面试。2019年,在经营自己的咨询公司和创业近两年之后,我决定回到全职工作岗位,发现自己又回到2015年的状态。

这一次,我在优步和谷歌、Facebook等其他顶级公司有了更大的朋友圈子,他们让我了解了真正的面试技巧。他们都必须同样努力学习。即使是那些没有像我一样从大学辍学的人。

那些留下来并完成了算法课程并获得了计算机科学学位的学生,他们仍然需要努力学习。

我放弃了幻想中的工程师可以一时兴起通过技术面试的想法,开始认识到现实情况,即技术面试就像学校里的SAT考试。即使你在高中花了四年时间学习所有的内容,如果你想要考得好,你仍然需要准备。就像sat考试一样你过去所有的学习和成绩都不会对最终的分数有影响。你的成功完全取决于你考试的表现。

一旦我意识到这个事实,每个人都需要学习,这对我来说是足够的动力去努力工作,因为我意识到这就是我的竞争对手在做的事情。通过这种动机,我形成了一个学习过程,帮助我通过了2019年的每一个技术面和现场面试。我是一个大学辍学生,通过了Stripe、Coinbase和Triplebyte的技术面试。我通过了谷歌、亚马逊(Amazon)、优步(再次)、Reddit、Squarespace和Braze的电话面试和现场面试。100%的通过率并不是意料中的,也不太可能再发生,但我相信专注于基础有助于实现这一目标。


谷歌送给通过面试的候选人礼物

这并非一帆风顺的。当我第一次开始准备面试时,我花了2个多小时几乎解决了现在被称为“leetcode简单”的问题,当时感觉不可能!我真的认为自己天生不够聪明,必须更加努力地学习,才能达到和谷歌工程师一样的水平,以同样的速度解决问题。

我说对了一件事,你需要练习。

我没有意识到的是,所有刚起步的人,包括那些谷歌工程师,在一开始也会遇到这种难度和挫败感。
我还是原来的我。现在和过去的唯一区别就是练习,练习,再练习。

人们常问的一个问题是“我是怎么学习?”不知道从哪里开始,下一步该做什么,如果你取得了进展,但很难坚持下去。当我在学习时,为了解决这些问题,我创建了一个Trello板,上面有我想学习的所有主题。看板会帮助我专注于最重要的问题,我应该学习和管理我的时间,以保持进步一致。

我在领英(LinkedIn)上发了一篇关于Trello看板的帖子,迅速走红。一位Trello PM联系我,说要为它创建一个官方的Trello模板,可以在这里找到:面试学习跟踪。
跟踪器提供了如何学习的模板。它需要确定主题列表,并搜索两个或两个以上的资源,这些资源可以告诉您有关主题的知识。然后给自己布置2-3道练习题来巩固自己的理解。可以通过任和途径找到资源,最重要的是内容本身。我发现我的大部分学习内容就是根据关键字通过谷歌搜索到的。


从不同的角度学习同一件事可以帮助你更好地理解它。例如,我在geeksforgeeks.org上读过一篇关于二叉搜索树的文章,也读过程序员面试宝典中关于二叉搜索树的章节。然后我会做2-3个练习题,或者让我觉得足够掌握了,然后再继续。

知识列表

下面列出了几乎所有的数据结构和算法,你可能在一个技术面试中遇到,以及一个非常简短的描述和一些资源,以开始建立你的学习计划。目标是为你提供一个起点来建立你的计划,而不是详细解释每个主题是什么。你计划的一部分是寻找资源,从不同的角度学习更多关于每个主题。

如果您没有听说过列表中的一些数据结构,或者之前没有对它们了解很多,它们可能看起来很复杂,但是随着您学习的进展,您很快就会体验到理解数组之前和之后的感受。

想想数组的概念对于非程序员来说是多么复杂,而对于最初级的开发人员来说又是多么基本。

这些数据结构只是帮助你创建算法的基础元素,一旦你花时间去理解它们,它们实际上会让事情变得更简单。就像一个数组维护有序元素列表所需的大量工作一样,这些数据结构包含函数和容器来高效地处理数据。

基本数据类型

这些是基本的数据类型。每个人都应该知道它们是如何工作的,何时使用它们,如何实现它们,以及它们背后折中的原因:

  • Array
  • Set
  • Hashmap
  • Linked List
  • Stack
  • Queue
  • Tree
  • Graph
    如果这个清单看起来很长,不要担心。很多数据结构是紧密相关的,甚至是建立在彼此之上的,所以你能够比你想象的更快地学习它们。

HashMap建立在数组之上。栈建立在链表或数组上。队列建立在linkedlist之上。树使用LinkedList的“节点”和“链接”的概念。图也是用相同的概念。它们和树也有很多相似之处。所有的树在技术上的都是图,所以你可以对它们应用很多相同的算法。

高级的数据结构

在掌握了基本的数据结构后,了解这些更高级的结构会给你更大的成功机会。有些问题在面试中经常出现,比如堆和二叉搜索树。LRU缓存和查找树出现的频率较低,但正变得越来越普遍。disjointed set和跳跃列表很少出现,但即使没有被问到,它们也会成为你想出有效解决方案的强大工具。

  • Heap (a.k.a Priority Queue)
  • LRU Cache
  • Binary Search Tree (AVL, Redblack)
  • Disjoint Set
  • Trie
  • Skip List
    当我们进入更高级的数据结构时,你会看到它们通常是使用基本数据类型作为基础的。对基本数据结构的良好理解将使学习这些高级数据结构变得容易得多。你会根据你已经知道的较小的结构来考虑它们,而不是新的大的复杂结构。

堆,也称为优先队列,是一种行为类似于队列的树。二叉搜索树(BSTs)是另一种可以更快地搜索节点的树。AVL和Redblack是两种流行的特殊类型的平衡二叉树。LRU缓存是结合Hashmap和LinkedList构建的内存高效缓存。

Trie是另一种可以快速搜索前缀/子字符串的树。Disjoint Sets是一种特殊类型的集合,它将其成员分隔成不重叠的子集,对联合查找算法很有用。跳跃列表是LinkedList的优化版本,它减少了查找特定节点所需的时间。

基本的搜索/遍历算法

所有的数据结构都是用来保存信息的。有些结构需要特殊的方式来有效地访问这些信息,这比简单地访问数组索引或散列表要复杂得多。数据结构(如树和图)保存数据并使用一些基本算法来访问其中的信息。

  • Breadth First Search (BFS)
  • Depth First Search (DFS)
  • Binary Search

高级搜索/遍历的算法

在这个领域有大量的研究,但对于一个技术面试来说,按频率排序,你可能遇到的最高级的搜索算法是:

  • Quick Select
  • Dijkstra
  • Bellman-Ford
  • A-star (rare)

排序算法

排序是用于提高解决方案性能的常用工具。有很多排序算法,但下面列出了最流行的面试算法。知道如何实现所有这些而不需要查阅任何资料。

  • Quick Sort
  • Merge Sort
  • Topological Sort
  • Counting Sort

重要话题

递归是一个非常重要的话题,在日常的软件工程中并不像在面试中那样经常出现。位操作一开始可能看起来很可怕,但是一旦你花时间去理解二进制数字格式和操作,你就会意识到它是多么的简单。

  • Recursion
  • Greedy Algorithms
  • Dynamic Programming
  • Bit Manipulation (AND, NOT, OR, XOR)

常用模式

这些模式可用于解决许多类似的算法问题

  • Backtracking 回溯
  • Two Pointers 双指针
  • Sliding Window 滑动窗口
  • Divide & Conquer 分治
  • Reservoir Sampling 蓄水池算法;

基于数学的问题

  • Permutations排列
  • Combinations组合
  • Factorial阶层
  • Power Set幂

其他常见问题

  • String to Integer 字符串转数字
  • Integer to String 数字转字符串
  • Adding huge numbers (that can’t fit into memory) 大数相加
  • Addition/Subtraction/Multiplication/Division without using operators不使用运算符加/减/乘/除

复杂度


虽然听起来很奇怪,但在面试中仅仅提供一个可行的解决方案是不够的。代码必须在“时间复杂度”和“空间复杂度”所描述的特定性能水平上运行。复杂性是用大O符号来描述的。

乍一看,这似乎很复杂(尤其是名字),但大多数人对此感到困惑的原因是,他们没有花时间去学习,只是在看到一些例子后凭直觉去做。与其依赖直觉,不如阅读Big-O符号以及如何确定算法的复杂性。你的面试官经常会问你,你的解决方案的时间和空间复杂度是多少。

实践

自我练习

没有实践,学习就没有意义。每次你从学习计划中学习到一个新话题,你就应该运用它。这将帮助你更长久地记住它,也给你一个更深刻的理解。为你完成的每个主题做一些练习题。像leetcode.com这样的网站可以让你用“树”或“图”来搜索相关的练习。

练习应该分两个阶段进行。第一阶段是当你第一次学习这个主题的时候。在这一阶段,你应该真正理解所涉及的内容以及你的解决方案为什么有效。第二个阶段是在您熟悉这些概念之后。在这一点上,您应该为自己计时,并以越来越快的解决问题为目标。

在面试中,根据问题的难度,你需要在15-45分钟内解决一个问题。当你刚开始的时候,可能会花几个小时去做第一个。过了最初的几个星期,时间应该开始减少。经过一两个月的持续练习(不仅仅是时间的流逝),你应该开始能够按时解决相当数量的问题。

有时,整个面试问题只涉及实现一个单一的数据结构,如LRU缓存或Trie。其他时候,它只涉及实现数据结构上的单个操作,比如对二叉搜索树的删除操作。

练习实现本文前面提到的所有数据结构,直到你不需要查找任何资料,你将在真正的面试中很容易地回答这些问题。专注于理解为什么它是以这种方式实现的,而不是试图记住确切的代码。根据理解接下来需要发生的事情而不是根据记忆来实现。

你可能不会被要求实现一个数组或平衡二叉树像AVL或红黑树,但你应该知道他们是如何工作的。

辅助练习(模拟面试)

仅仅练习问题是不够的。在面试中,你将与一个真实的人进行互动,他会实时判断你的技能。这种情况比你在家里学习时压力更大。为了避免真正的面试的尴尬和焦虑,你应该在面试前多做一些练习。这将帮助你更放松,你就可以专注于面试问题,而不是被非技术性的东西分散注意力。

这种练习面试被称为模拟面试。你可以让一个朋友帮你,或者在网上找一个能帮你找到面试官的服务。

知道你什么时候准备好

不管你学习多久,你永远都不会觉得“准备好了”。总有更多的东西需要深入研究,或者薄弱领域需要更多练习。

在整个过程中肯定有运气的因素。很有可能你遇到的问题是关于一个你根本没有准备过的话题,或者薄弱的方面。也有可能,你得到的所有问题都是你两个月前真正擅长的主题,而不需要花额外的时间学习通过。你不可能为每一个可能的结果做好准备,但是凭借多练习你的成功机会会比其他人多。

跟踪你的进展并得到反馈是非常重要的,这能让你知道你什么时候真正准备好在面试中表现,而不是仅仅依靠情绪。 当你做练习题的时候,记录下你的完成时间,在leetcode.com或hackerrank.com网站上,中等水平的问题不超过40分钟,高水平的问题不超过1小时。

认识到你必须通过每一个边界情况,它依靠这些网站,但幸运的是,这不是真正的面试情况!大多数面试代码都是在少数边界情况下进行评估的,小错误会被通过,或者认为是不相关的。评判的意义不是严格到完美,但面试官需要看到足够的“信号”来认可你。

在进行真正的面试之前,至少要进行三次模拟面试。只有当你在练习中始终表现良好时,才能继续前进。

大型科技公司不停地招聘,所以他们在安排或重新安排你的面试日期上非常灵活。我就把谷歌和Uber的面试分别重新安排了2个月和1个月,因为我还没准备好。

你不需要任何特别的借口来重新安排面试。我只是告诉他们“我需要更多的时间来准备”。这些公司会为你的面试投入很多钱。5-6个全职员工会对你进行4-5个小时的面试,然后对结果进行评估。考虑到招聘人员花时间找到你,每次面试至少要几千美元成本。

他们希望你有备而来。他们宁愿等待,也不愿浪费时间、金钱和拒绝一名工程师的机会成本,因为如果他们多等一两个月,工程师本可以通过考试。如果你没准备好,就推迟面试吧。没有准备就不要进去!

如果你按照上面的方法去做,那么知道自己什么时候准备好就很容易了。你可能仍然觉得没准备好,但你可以确定,因为你已经完成了你的任务,并且通过了很多模拟面试。毫无疑问,做好准备意味着你已经通过练习证明,在相同的时间限制下问相同类型的难题,你可以通过面试。

本文只关注数据结构和算法问题。系统设计和行为问题在招聘决策中也扮演着重要的角色。我将把这些主题留给另一篇文章。

想要了解更多的技巧,请查看我的播客面试,关于如何让你的简历被雇主注意到,如何准备技术面试,以及如何进行offer谈判。

Books

Cracking the Coding Interview 6th Edition

Elements of Programming Interviews

JavaScript Algorithms

你可能感兴趣的:(如何学习数据结构和算法)