【前端算法面试学习之路】基础知识 + 真题训练 + 逻辑解读(持续更新中)

424 阅读10分钟

基础知识就像是一座大楼的地基,它决定了我们的技术高度。而要想快速做出点事情,前提条件一定是基础能力过硬,“内功”要到位。 ——数据结构与算法之美 本文总结了一些入门数据结构与算法的概念,以及一些常用的基础数据结构的特点、优缺点。只有理解了每一种数据结构存在的意义;在遇到问题时,才能拥有选择最适合的数据结构和解决方法的能力。

一、数据结构与算法的一些简单概念

  • 什么是数据结构 先来看看官方的解释:数据结构是计算机存储、组织数据的方式,是指相互之间存在一种或多种特定关系的数据元素的集合

直白地理解,就是数据存储的方式

我们知道,数据的存储都是有目的的,都是为了方便后续对这些数据的再利用。存储没有价值的数据就是在对存储空间耍流氓。而在存储空间中,数据的存放是什么样子的呢?那必不可能是乱放的,要不然当你要用它的时候,你怎么找到它呢?这其实就是数据结构存在的意义

这时候又有问题冒出来了,既然这样,那么所有的数据都按一种数据结构的方式来存储岂不是更方便。这里以数组为例[2,3,5,6],用数组来存放这样一段数据是完全没问题的;但如果用数组来存放这些数据[father,brother,sister],数据存储是没有问题,但你怎么能够体现这些数据之间的逻辑关系呢?如果无法体现,后期的使用自然也是个大问题。所以,现有的数据结构已经有很多,而每一种都有其独特的特点,掌握了这些特点,才能更好地解决问题。

  • 什么是时间复杂度,什么是空间复杂度
  1. 时间复杂度是指执行这个算法所需要的工作量
  2. 空间复杂度是指执行这个算法所需要的内存空间 这两者的概念还是比较好理解的,算法的复杂度是怎么体现的呢?或者说是怎么表示的? 这里用到的就是大O表示法
  • 大O表示法
  1. 大O表示法指出了算法有多快。举个例子讲:假设一个列表包含n个元素。简单查找需要检查每个元素,因此需要执行n次操作,这个算法的运行时间为O(n)。单位呢? 没有——大O表示法指的并非是以秒为单位的速度。它指出了算法运行时间的增速。
  2. 怎么理解算法运行时间的增速这句话呢,同样用个例子讲,假如我要编写一个查找算法,在100个元素中查找,如果我检查一个元素需要1毫秒,使用简单查找时,我就必须检查100个元素,就需要100毫秒;而使用二分查找,只需检查log2(100)~ 7个元素,因此只需要7毫秒。但如果查找的列表中有10000个,10亿个元素呢?他们各自的查找时间是什么样的?

如果按速度的思路讲,如果查找的列表有10亿个元素,运用二分查找,运行时间是30毫秒(log2(1000000000))。而二分查找的速度大约是简单查找的15倍,所以简单查找需要450毫秒,这是正确的吗?

大错特错,如果列表包含10亿元素,简单查找需要10亿毫秒,这是因为二分查找和简单查找的运行时间的增速不同。也就是说,随着元素的增加,二分查找需要的额外时间并不多,而简单查找需要的额外时间却很多。因此,随着列表的增长,二分查找的速度比简单查找快得多。

通过上面的例子,相信对大O表示法的理解就比较清晰了,有疑问的话可以在评论区讨论。

二、常考的基础数据结构

线性结构

  • 什么是线性结构 线性结构就是n个数据元素的有序集合。也即是数据元素之间存在着'一对一'线性关系的数据结构。如果官方的还不能理解,来看看这几个特点,应该就清晰了: 1.集合中必存在唯一的一个"第一个元素"; 2.集合中必存在唯一的一个"最后的元素"; 3.除最后元素之外,其它数据元素均有唯一的"后继"; 4.除第一元素之外,其它数据元素均有唯一的"前驱"

1、数组

一维数组

数组是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。 ——数据结构与算法之美

  • 特点
  1. 数组是使用一组连续的内存空间来存储数据。
  2. 数组可以直接通过下标访问,所以数组的查找效率很高,时间复杂度仅为O(1)。
  3. 数组的每次插入和删除操作都需要将操作位置后面的元素后移或前移(原因极为第一点:数组是一组连续的内存空间),所以这就导致数组的插入和删除效率很低。 注意:JS中的数5组比较特别,它未必满足上文的第一条特点

在JS中,如果我们3在一个数组中只定义了一种类型的元素,如:

const arr1 =[1,3,5,9]

它是一个纯数字数组,那么对应的确实是连续的内存,但当我们定义了不同类型的元素:

const arr2 = ['hello',1,{a:1}]

它对应的就不是一段连续的内存,其底层使用的是哈希映射内存空间,是由对象链表来实现的。 所以可以得出的结论是:JS中的数组未必是真正的数组。而所谓“真正的数组”的判断条件为:存储在连续的内存空间中

二维数组 —— 矩阵

2、特殊的数组

队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作。而在表的后端进行插入操作,和栈一样,队列是一种操作受限的线性表。

  • 特点
  1. 进行插入操作的端称为队尾
  2. 进行删除操作的端称为队头
  3. 先进先出原则
  • 栈和队列的区别
  1. 栈是一端封口,而队列是的两端全是开口
  2. 栈先进后出,队列先进先出
  • 队列的实现方式
  1. 顺序队列,基于顺序表实现的队列
  2. 链队列,基于链表实现的队列 二者区别仅是顺序表和链表的区别,即在实际的物理空间中,数据集中存储的队列是顺序队列,分散存储的队列是链队列。
  • 队列的实际应用 医院中的排队挂号就是一种典型的队列的应用,先到的先出,插入的叫队尾,队头先出队。

栈是一种先入后出的数据结构,可以使用数组来模拟。

  • 注意:在使用栈的时候
  1. push入栈,就只能用pop出栈
  2. unshift入栈,就只能用shift出栈

3、链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

  • 链表的特点
  1. 组成结构:链表由一系列结点组成,结点可以在运行时动态生成。每个结点包括两个部分:一个存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
  2. 存储特点:可以用任意一组存储单元存储线性表的数据元素(这组存储可以是连续的,也可以不是)
  • 相比于其他数据结构的优缺点
  1. 相比于数组 优:链表可以克服数组需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。 劣:失去了数组随机读取的优点,同时因为增加了结点的指针域,空间开销更大。

树型结构

  • 什么是树型结构

树形结构是一层次的嵌套结构。 一个树形结构的外层和内层有相似的结构, 所以这种结构多可以递归的表示。经典数据结构中的各种树状图是一种典型的树形结构:一颗树可以简单的表示为根, 左子树, 右子树。 左子树和右子树又有自己的子树。

  • 特点
  1. 树形结构是一种非线性数据结构。它的数据元素之间存在着“一对多”的树形关系
  2. 因为一个树形结构的外层和内层有相似的结构,所以一旦使用这种数据结构,可以考虑使用递归解题。
  3. 在树形结构中,树根结点没有前驱结点,其余每个结点有且仅有一个前驱结点。叶子结点没有后续结点,其余每个结点的后续结点数可以是一个或多个。

特殊的树 —— 二叉树

图形结构

  • 什么是图型结构

图形结构是一种比树形结构更复杂的非线性结构。

  • 特点
  1. 在树形结构中 在树形结构中,每一层上的结点只能和上一层中的至多一个结点相关,但可能和下一层的多个结点相关。
  2. 在图形结构中 任意两个节点之间都可能相关,即结点之间都可能相关。

集合结构

  • 什么是集合结构

处于同一数据集合中的元素之间除同属该集合这一联系外没有其他的关系。如公共汽车上的所有乘客,存放在仓库中的产品。集合中的主要操作有查找和排序。集合结构的元素间没有固有的关系,不需要存储关系,往往借助于其他数据结构,如线性表和树。

  • 注:唯一专用于集合类型的数据结构是哈希表

真题解读

数组的应用

1、(1)两数之和

做算法题的思路:拿到题目第一步

  1. 去想暴力解法
  2. 去想暴力解法的优化方法:1、空间换时间

这道题的思路:

  1. 暴力解法:双层循环直到找到里外两层相加等于指定值,返回
  2. 空间换时间,使用Map
  3. 遍历数组,并维系一个Map,记录已经遍历过的值和对应下标,每遍历一个新数字,都去Map中查询该值和targetNum 重要结论
  • 几乎所有的求和问题,都可以转化为求差问题