「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」
首先,这是一篇正儿八经的技术文章。看完这篇文章,你可以学到:
- 什么是链表
- 数组如何转换成链表
- 链表如何转换成数组
- 链表的常见操作
- 链表在前端中的使用场景
一、什么是链表
链表,又可以分为单向链表和双向链表。像上图所示就是一个单向链表,每个节点只负责链接它的下一个节点。 而双向链表则是在单向链表的基础上,加多了一个链接上一个节点的功能,可以做到来回传递。
单向链表的数据结构大致是这样子的:
const linkedList = {
val: 1,
next: {
val: 2,
next: {
val: 3,
next: {
val: 4,
next: null
}
}
}
}
二、数组如何转换成链表
数组转列表可以拆分成两个步骤,第一个步骤把当前的值转换成链表节点,需要一个构造函数ListNode, 然后只需要跑一遍for循环,做两个动作:
- 通过构造函数把每个节点转成链表节点;
- 把下一个数设置为当前节点的后继节点。
function ListNode(val) {
this.val = val;
this.next = null;
}
function generateList(array) {
// 非空判断
if (!array || !array.length) {
return null;
}
let node = new ListNode(array[0]);
let current = node;
for (let index = 1; index < array.length; index++) {
current.next = new ListNode(array[index]);
current = current.next;
}
return node;
}
三、链表如何转换成数组
链表转数组就更加简单不过了。因为链表你每次可以通过当前节点的next,拿到下一个节点,只需要一个while循环即可遍历整个链表。
function generateArray(list) {
let res = [];
while (list) {
res.push(list.val);
list = list.next;
}
return res;
}
四、链表的常见操作
这一部分我结合leetcode上的真题来举例说明
1.翻转链表
剑指 Offer II 024. 反转链表
function reverseList(head) {
let prev = null;
let cur = head;
while (cur) {
const next = cur.next;
cur.next = prev;
prev = cur;
cur = next
}
return prev;
};
代码解析: 实际上就是从头节点开始, 我们不断记录上一轮的结果, 然后每次把当前节点的下一个节点,指向上一轮记录的结果。相当于我们在原数组不断做shift,取出第一个值, 然后通过unshift去插入到结果数组中一样。只不过在链表中改成了next的指向问题。
function mergeTwoLists(l1, l2) {
if (!l1) return l2;
if (!l2) return l1;
\
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
};
代码解析: 合并两个有序链表的话,我们只需要考虑当前值谁大谁排在前面,然后再次比较大的那个链表的下一个数字和另一个链表当前数字的大小,递归直到一方完全排完了。就把另一方的剩余数据都放在后面即可。
3.给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
**个位置。
61. 旋转链表
function rotateRight(head, k) {
if (k === 0 || !head || !head.next) {
return head;
}
let count = 1;
let cur = head;
while (cur.next) {
count++;
cur = cur.next;
}
let index = count - k % count;
if (index === count) {
return head;
}
cur.next = head;
while (index > 0) {
index--;
cur = cur.next;
}
const ret = cur.next;
cur.next = null;
return ret;
};
题目说明: 可能很多同学一开始看到旋转列表会以为跟翻转列表是一回事,实际上还是有差距的。翻转的话是从前到后整个反过来。 旋转的话可能是把最后几个元素挪到最前面。
代码解析: 我把这道题目拆成四个步骤来做
- 求链表的总长度;
- 计算出链表的总长度 - k的差值,不过考虑到k可能比总长度大, 所以做一个求余操作,这一步决定我们待会儿从哪里切割链表;
- 把链表的头尾节点链接起来, 形成闭环,这样子就不用你切割完后面的那截链表,还得跑去前面一个一个拼接;
- 从刚刚步骤2算出的位置进行切割,这样就实现了链表的旋转。
五、链表在前端中的使用场景
1. 原型链
前端的原型链本质上就是链表,可以层层传递,而当我们要在原本的Parent和Child这两个类中插入一个叫做Connect的中间类的时候,我们用到的也是类似于链表插入的功能,把Connect的原型设置成Parent,再把Child的原型设置成Connect。
2. 链式编程
在维护一些祖传代码的时候,很多时候我们能不改动原本的代码就尽量别改,因为你可能因为一个小改动,修复了一个bug从而带来更多的bug,但是如果原本的数据结构发生了改变时候怎么办呢?这时候我们可以新增一个format转换的中间方法,新写一个方法,把当前的数据结构转换成原本的数据结构后进行使用。这也是链式编程当年风靡全球的一大亮点。
码字不易,喜欢的小伙伴们可以点个赞支持一下~!