一、链表
链表(Linked List)是一种常见的基础数据结构,也是线性表的一种。
一个线性表是 n 个具有相同特性的数据元素的有限序列,线性表的存储结构分为两类:顺序表(数组)和链表。
链表相比较顺序表,它并不会按照线性的顺序存储数据,而是在每个节点里存储到下一个节点的指针,在 JavaScript 中,我们可以这样描述链表中的节点:
二、链表 vs 数组
存储方式的不同:
-
数组在使用前需要先申请占用内存的大小,并且是连续的内存区域,不适合 动态存储,正是由于连续内存存储,使得 数组随机访问的时间复杂度为 O(1)。
-
链表则克服了数组需要预先知道数据大小的缺点,可以充分地利用内存空间,实现动态内存管理,但是由于每个节点增加了指针域,空间开销比较大。
操作时间复杂度的不同:
数据类型 | 读取时间复杂度 | 写入时间复杂度 |
---|---|---|
链表 | O(n) | O(1) |
数组 | O(1) | O(n) |
前面从存储方式的分析中,可以知道数组具备随机访问的能力,但是访问链表中的元素则需要遍历链表,因此时间复杂度为 O(n)。 链表中写入操作只需要将当前节点的前驱和后继节点的指针断开即可,所以时间复杂度为 O(1)。 但是由于数组是连续内存的特性,写入操作并没有那么简单,以删除数组首位元素为例,数组需要执行以下两步操作:
- 删除首位元素。O(1)
- 从第二位元素开始,依次向前移动一位。O(n)
所以对于任意位置的写入,链表虽然需要先执行 O(n) 的遍历来定位元素,但是它的整体效率仍然比数组高。
三、Easy 典型题型分析
1、【1290. 二进制链表转整数】
给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。请你返回该链表所表示数字的 十进制值 。
这道题目主要考察链表遍历的基本操作:迭代链表节点的 next 指针。
2、【876. 链表的中间结点】
给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
这道题目比较实在的解题思路是:第一次遍历求出链表长度,从而计算出中间位置,第二次遍历根据中间位置找出中间节点。
下面给出的解法,是经常用到的双指针技巧中的快慢指针,巧妙地求解出中间节点:
3、【83. 删除排序链表中的重复元素】
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
由于本道题目中的链表是一个排序链表,所以只考察了链表中删除节点的操作:**改变目标节点的前驱节点的 next 指针,即可删除目标节点。**参考视频:传送门
4、【206. 反转链表】
反转一个单链表。
第一种解法:先遍历链表获取翻转后的链表节点值的数组,再遍历链表替换节点的值。
第二种解法,利用链表的特性,简化为一次遍历完成翻转操作。
以上面的链表为例,翻转流程如下:
解题代码如下:
5、【141. 环形链表】
给定一个链表,判断链表中是否有环。
第一种解法:遍历链表,利用 HashMap 记录节点对象,如果出现重复的节点则有环。
第二种解法是采用双指针中的快慢指针技巧:当链表中存在环时,快指针必然能追上慢指针。