数组
- 跟其他编程语言一样,JS 数组也是一组有序的数据,但跟其他语言不同的是,数组中的每个槽位可以存储任意类型的数组。
- JS 数组的动态大小的,会随着数据添加而自动增长。
创建数组
使用 Array 构造函数
创建一个空数组:
1 | // 创建一个空数组 |
创建一个指定长度的数组,元素为 undefined
:
1 | // 创建一个长度为 5 的数组,元素为 undefined |
创建并初始化数组:
1 | // 创建一个包含 [1, 2, 3] 的数组 |
使用数组字面量
创建一个空数组:
1 | const arr = []; // 创建一个空数组 |
直接定义数组元素:
1 | // 创建一个包含 [1, 2, 3] 的数组 |
使用 Array.from
方法
从类数组对象或可迭代对象创建数组:
1 | // 创建一个包含 [1, 2, 3] 的数组 |
从字符串转换为数组:
1 | // 创建一个包含 ['h', 'e', 'l', 'l', 'o'] 的数组 |
使用 Array.of
方法
1 | // 创建一个包含 [1, 2, 3] 的数组 |
使用 Array.fill
方法
1 | // 创建一个长度为 5 的数组,元素全为 0。时间复杂度为 O(N) |
插入元素
在末尾插入元素
时间复杂度为 O(1)
1 | const arr = [1, 2, 3]; |
在开头插入元素
时间复杂度为 O(N)
1 | const arr = [1, 2, 3]; |
在任意位置插入元素
时间复杂度为 O(N)
splice()
方法可以在数组的任意位置插入元素。它接受三个参数:
- 起始索引
- 要删除的元素的数量(如果不需要删除元素,设置为
0
) - 要插入的元素(可以是一个或多个)
1 | const arr = [1, 2, 3]; |
删除元素
从末尾删除元素
时间复杂度为 O(1)
1 | const arr = [1, 2, 3]; |
从开头删除元素
时间复杂度为 O(N)
1 | const arr = [1, 2, 3]; |
在任意位置删除元素
时间复杂度为 O(N)
splice()
方法可以删除数组的任意位置元素。它接受两个参数:
- 起始索引
- 要删除的元素的数量(如果不需要删除元素,设置为
0
) - 要插入的元素(可以是一个或多个)
1 | const arr = [1, 2, 3]; |
修改元素
根据索引修改元素
时间复杂度为 O(1)
1 | const arr = [1, 2, 3]; |
使用 splice
方法
时间复杂度为 O(N)
splice()
方法可以删除数组的任意位置元素。它接受两个参数:
- 起始索引
- 要删除的元素的数量(如果不需要删除元素,设置为
0
) - 要插入的元素(可以是一个或多个)
当你需要修改数组中连续的多个元素时,splice()
方法非常有用。
1 | const arr = [1, 2, 3]; |
查询元素
根据索引查询元素值
时间复杂度为 O(1)
1 | const arr = [1, 2, 3]; |
根据元素值查找索引
时间复杂度为 O(N)
1 | const arr = [1, 2, 3]; |
链表
链表(Linked List)是一种常见的线性数据结构,它由一系列节点(Node)组成,每个节点包含两部分:数据域和指针域。数据域用于存储实际的数据,指针域则存储指向下一个节点的引用(在单链表中),通过这些指针将各个节点链接起来形成一个链式结构。
链表存储有序的元素集合,但不同于数组,链表中的元素在内存中不是连续放置的,而是通过指针相互关联。
链表的特点
- 插入和删除的效率高:在链表中插入或删除节点,只需要修改指针,时间复杂度为 O(1)。
- 随机访问效率低:由于链表中的节点在内存中不是连续存储的,不能像数组那样通过下标直接访问元素,需要从头节点开始依次遍历,时间复杂度为 O(n)。
链表的类型
- 单链表:每个节点包含一个数据元素和一个指向下一个节点的指针。链表的最后一个节点的指针通常指向
null
,表示链表的结束。 - 双向链表:每个节点除了包含数据元素和指向下一个节点的指针外,还包含一个指向前一个节点的指针。这种结构允许在链表中双向遍历。
- 循环链表:单循环链表的最后一个节点的指针指向头节点,形成一个闭环;双循环链表则是双向链表的首尾相连形成闭环。
创建链表
节点类的实现
- 链表最后一个节点的下一个元素始终是
undefined
或null
。 - 当一个 Node 实例被创建时,它的
next
指针总是undefined
。这没问题,因为我们知道它会是链表的最后一项。
1 | class ListNode { |
链表类的实现
1 | class LinkedList { |
向链表尾部添加元素
1 | class LinkedList { |
循环迭代链表直到目标位置
循环到目标 index 的代码片段在 LinkedList 类的方法中很常见、因此我们将其提取到一个单独的方法中,这样就可以在不同的地方复用它。
注意:循环停止条件是小于目标 index,而不是小于等。这是因为目标的前一个节点的 next 就是目标节点。
1 | class LinkedList { |
从链表中移除元素
移除指定位置的元素
- 移除第一个元素:要做的就是让
head
指向列表的第二个元素。我们将用current
变量创建一个对链表第一个元素的引用。这样current
变量就是对链表中第一个元素的引用。如果把head
赋为current.next
,就会移除第一个元素。 - 移除最后一个或中间某个元素:需要迭代链表的节点,直到找到要移除元素的前一个元素,我们用
previous
变量表示对当前元素的前一个元素的引用。在迭代到目标元素之后,current
变量会持有我们想从链表中移除的节点。因此,要从链表中移除当前元素,要做的就是将previous.next
赋值为current.next
。
1 | class LinkedList { |
验证:
1 | const list = new LinkedList(); |
如图可以看到,10 被成功移除了。
在任意位置插入元素
1 | class LinkedList { |
注意:使用变量引用我们需要控制的节点非常重要,这样就不会丢失节点之间的链接。我们可以只使用一个变量(previous),但那样会很难控制节点之间的链接。
因此,最后声明一个额外的 current 变量来帮助我们处理这些引用。
验证:
1 | const list = new LinkedList(); |
如图,我们成功在索引 1 的位置插入了 15。
indexOf
1 | const defaultEquals = (a, b) => { |
- 一如既往,需要一个变量来帮助我们循环访问列表。该变量是 current,它的初始值是 head。
- 然后迭代元素,从 head(索引 0) 开始,直到链表长度(count 变量)为止。为了确保不会发生运行时错误,我们可以验证下 current 变量是否为 null 或 undefined。