代码随想录算法训练营005| 一周总结| 数组、链表

数组基础知识总结

  1. 集合列表和数组

    • 集合

      • 定义:由一个或多个确定的元素所构成的整体。通俗来讲,集合就是将一组事物组合在一起
      • 特点:
        集合里的元素类型不一定相同
        集合里的元素没有顺序
    • 列表

      • 定义:是一种数据项构成的有限序列,即按照一定的线性顺序,排列而成的数据项的集合。列表的概念是在集合的特征上形成的,它具有顺序,且长度是可变的。
      • 特点:
        列表中没有索引
        列表中的元素在内存中可能彼此相邻,也可能不相邻。
    • 数组
      数组是由有限个相同类型的变量所组成的有序集合。

  2. 数组在内存中的存储方式
    数组中的元素在内存中是连续存储的,且每个元素占用相同大小的内存。而不同类型的数组,每个元素所占的字节个数也不同。
    总结: 物理存储方式是顺序存储, 访问方式是随机访问

  3. 数组的基本操作
    a. 读取元素
    由于数组在内存中顺序存储,所以只要给出一个数组下标,就可以读取到对应的数组元素,下标一般从 0 开始。

    示例:

    var array = [3, 1, 2, 5, 4, 9, 7, 2];
    //输出数组中下标为3的元素
    console.log(array[3]);
    

    注意输入的下标必须在数组的长度范围之内,否则会出现数组越界。
    时间复杂度:O(1)

    b. 查找元素
    与读取元素类似,由于我们只保存了索引为 0 处的内存地址,因此在查找元素时,只需从数组开头逐步向后查找就可以了。如果数组中的某个元素为目标元素,则停止查找;否则继续搜索直到到达数组的末尾。
    时间复杂度:O(n)

    c. 更新元素
    要把数组中某一个元素的值替换为一个新值,直接利用数组下标,就可以把新值赋给该元素。

    var array = [3, 1, 2, 5, 4, 9, 7, 2];
    // 给数组下标为5的元素赋值
    array[5] = 10;
    //输出数组中下标为5的元素
    console.log(array[5]);
    

    时间复杂度:O(1)

    d. 插入元素

    • 尾部插入
      尾部插入,直接把插入的元素放在数组尾部的空闲位置即可。
    • 中间插入
      由于数组的每一个元素都有其固定下标,所以不得不首先把插入位置及后面的元素向后移动,腾出地方,再把要插入的元素放到对应的数组位置上。
    • 超范围插入
      假如现在有一个长度为 6 的数组,已经装满了元素,这时还想插入一个新元素, 这就涉及数组的扩容。此时可以创建一个新数组,长度是旧数组的 2 倍,再把旧数组中的元素统统复制过去,这样就实现了数组的扩容。

    e. 删除元素
    数组的删除操作和插入操作的过程相反,当我们删除掉数组中的某个元素后,数组中会留下空缺的位置,而数组中的元素在内存中是连续的,这就使得后面的元素需对该位置进行填补操作。如果删除的元素位于数组中间,其后的元素都需要向前挪动 1 位。
    时间复杂度:O(n)

  4. 数组的优劣势

    • 优势
      数组拥有非常高效的随机访问能力,只要给出下标,就可以用常量时间找到对应元素。有一种高效查找元素的算法叫作二分查找,就是利用了数组的这个优势。
    • 劣势
      体现在插入和删除元素方面。由于数组元素连续紧密地存储在内存中,插入、删除元素都会导致大量元素被迫移动,影响效率。
      总的来说,数组所适合的是读操作多、写操作少的场景。
  5. 二维数组
    二维数组是一种结构较为特殊的数组,只是将数组中的每个元素变成了一维数组。

    ![image.png](https://img-blog.csdnimg.cn/img_convert/bd09f14edc2c20cf7e2446a774308e90.png#clientId=ua98ff845-cabe-4&from=paste&height=201&id=u732a90c4&margin=[object Object]&name=image.png&originHeight=401&originWidth=735&originalType=binary&ratio=1&size=39142&status=done&style=none&taskId=u5c123556-c726-4607-b272-6f30b95605f&width=367.5)

    所以二维数组的本质上仍然是一个一维数组,内部的一维数组仍然从索引 0 开始,我们可以将它看作一个矩阵,并处理矩阵的相关问题。

    示例
    类似一维数组,对于一个二维数组 A = [[1, 2, 3, 4],[2, 4, 5, 6],[1, 4, 6, 8]],计算机同样会在内存中申请一段 连续 的空间,并记录第一行数组的索引位置,即 A[0][0] 的内存地址,它的索引与内存地址的关系如下图所示。
    ![image.png](https://img-blog.csdnimg.cn/img_convert/9eb27f734e435fa86ca147590f29e4dc.png#clientId=ua98ff845-cabe-4&from=paste&height=211&id=uf79f4a67&margin=[object Object]&name=image.png&originHeight=421&originWidth=1172&originalType=binary&ratio=1&size=153086&status=done&style=none&taskId=u9019c46d-c5c7-4e6d-848a-1749e04a6b8&width=586)

    注意,实际数组中的元素由于类型的不同会占用不同的字节数,因此每个方格地址之间的差值可能不为 1。
    实际题目中,往往使用二维数组处理矩阵类相关问题,包括矩阵旋转、对角线遍历,以及对子矩阵的操作等。

  6. 数组常用解题方法

    • 二分法
    • 双指针法
    • 滑动窗口
    • 模拟
  7. 什么是链表
    链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。

  8. 链表在内存中的存储方式
    链表采用了见缝插针的方式,链表的每一个节点分布在内存的不同位置,依靠 next 指针关联起来。这样可以灵活有效地利用零散的碎片空间
    总结:物理存储方式是随机存储, 访问方式是顺序访问

  9. 链表的分类

    • 单向链表
      单向链表的每一个节点又包含两部分,一部分是存放数据的变量 data,另一部分是指向下一个节点的指针 next。
      ![image.png](https://img-blog.csdnimg.cn/img_convert/032e481b68d0dfe61a9322ed2ec3d92a.png#clientId=u97c2adef-1b0f-4&from=paste&height=84&id=uaf4e3255&margin=[object Object]&name=image.png&originHeight=168&originWidth=1000&originalType=binary&ratio=1&size=87259&status=done&style=none&taskId=ud4cf89b0-69eb-47f3-a09f-52b06dcd38b&width=500)

    • 双向链表
      双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有 data 和 next 指针,还拥有指向前置节点的 prev 指针
      ![image.png](https://img-blog.csdnimg.cn/img_convert/3b9f5f586e9454ac3237df31ad7c9df6.png#clientId=u97c2adef-1b0f-4&from=paste&height=68&id=ud9f292c4&margin=[object Object]&name=image.png&originHeight=136&originWidth=979&originalType=binary&ratio=1&size=69437&status=done&style=none&taskId=u11851a4e-4ce1-4035-90e9-cbd5e2f13ec&width=489.5)

  10. 链表的基本操作
    a. 查找节点
    在查找元素时,链表不像数组那样可以通过下标快速进行定位,只能从头节点开始向后一个一个节点逐一查找。
    链表中的数据只能按顺序进行访问,最坏的时间复杂度是 O(n)。

    b. 更新节点
    如果不考虑查找节点的过程,链表的更新过程会像数组那样简单,直接把旧数据替换成新数据即可。
    ![image.png](https://img-blog.csdnimg.cn/img_convert/76f625697f06cf1abf9ccc218b8f3683.png#clientId=ufc51d92b-d3c2-4&from=paste&height=123&id=u434d048b&margin=[object Object]&name=image.png&originHeight=246&originWidth=994&originalType=binary&ratio=1&size=143606&status=done&style=none&taskId=uf83d9542-288d-4f46-a2ba-34d099e1c3d&width=497)

    c. 插入节点

    • 尾部插入
      把最后一个节点的 next 指针指向新插入的节点即可。
      ![image.png](https://img-blog.csdnimg.cn/img_convert/10e2298688e4757ca73820719ac7c4cd.png#clientId=ufc51d92b-d3c2-4&from=paste&height=139&id=u1679b0ca&margin=[object Object]&name=image.png&originHeight=278&originWidth=925&originalType=binary&ratio=1&size=142455&status=done&style=none&taskId=uf626d03e-ef12-4448-81a8-111ada6fb9e&width=462.5)

    • 头部插入
      第 1 步:把新节点的 next 指针指向原先的头节点。
      第 2 步:把新节点变为链表的头节点。
      ![image.png](https://img-blog.csdnimg.cn/img_convert/6c988fa66d20b9b4effd617d5cff3007.png#clientId=ufc51d92b-d3c2-4&from=paste&height=196&id=u5533fbc2&margin=[object Object]&name=image.png&originHeight=392&originWidth=956&originalType=binary&ratio=1&size=196973&status=done&style=none&taskId=u9e33ff00-2277-41e6-9dbb-25f03d0a18b&width=478)

    • 中间插入
      第 1 步:新节点的 next 指针,指向插入位置的节点。
      第 2 步:插入位置前置节点的 next 指针,指向新节点。
      ![image.png](https://img-blog.csdnimg.cn/img_convert/3c9c88ce5aa7a7a2e59239026d910977.png#clientId=ufc51d92b-d3c2-4&from=paste&height=241&id=u17f9bb6d&margin=[object Object]&name=image.png&originHeight=482&originWidth=935&originalType=binary&ratio=1&size=190401&status=done&style=none&taskId=u3130a7a9-0206-48cf-acc4-e49b492bb94&width=467.5)
      d. 删除节点

    • 尾部删除
      把倒数第 2 个节点的 next 指针指向空即可。
      ![image.png](https://img-blog.csdnimg.cn/img_convert/b2690abf0037f12558c57baaf8646e5f.png#clientId=ufc51d92b-d3c2-4&from=paste&height=160&id=uaad29749&margin=[object Object]&name=image.png&originHeight=319&originWidth=959&originalType=binary&ratio=1&size=176066&status=done&style=none&taskId=udd65c45d-4d31-4dc8-a1b9-5406d95a797&width=479.5)

    • 头部删除
      把链表的头节点设为原先头节点的 next 指针即可。
      ![image.png](https://img-blog.csdnimg.cn/img_convert/e74e9404d97f39b598ee0b2a2fb68315.png#clientId=ufc51d92b-d3c2-4&from=paste&height=170&id=u8e0e0e20&margin=[object Object]&name=image.png&originHeight=340&originWidth=1047&originalType=binary&ratio=1&size=171752&status=done&style=none&taskId=ua55d0dc4-ac55-4dfd-9455-0226605cd68&width=523.5)

    • 中间删除
      把要删除节点的前置节点的 next 指针,指向要删除元素的下一个节点即可。
      ![image.png](https://img-blog.csdnimg.cn/img_convert/e97eedf0a979dcb87001b5f73f19232d.png#clientId=ufc51d92b-d3c2-4&from=paste&height=177&id=uc6c97fcf&margin=[object Object]&name=image.png&originHeight=353&originWidth=1056&originalType=binary&ratio=1&size=174956&status=done&style=none&taskId=u4fc0c226-c649-4e19-9e7f-53144a956e7&width=528)

  11. 链表常用解题方法

    • 双指针法
  12. 数组 VS 链表

    查找 更新 插入 删除
    数组 O(1) O(1) O(n) O(n)
    链表 O(n) O(1) O(1) O(1)

    从表格可以看出,数组的优势在于能够快速定位元素,对于读操作多、写操作少的场景来说,用数组更合适一些。
    相反地,链表的优势在于能够灵活地进行插入和删除操作,如果需要在尾部频繁插入、删除元素,用链表更合适一些。

你可能感兴趣的:(代码随想录算法训练营,链表,算法,数据结构)