- 多个元素组成的列表
- 元素存储不连续,用 next 指针连在一起
- 单向链表
{ value, next }
- 双向链表
{ value, prev, next }
数组 vs 链表
- 数组:增删非首尾元素时往往需要移动元素。
- 链表:增删非首尾元素,不需要移动元素,只需要更改 next 的指向即可
获取、修改元素时,数组效率高
添加、删除元素时,链表效率高
- js 中类似于链表的典型就是原型链
- 但是 js 中没有链表这种数据结构,我们可以通过一个 object 来模拟链表
链表操作:
const a = { val: "a" }
const b = { val: "b" }
const c = { val: "C" }
const d = { val: "d" }
a.next = b
b.next = c
c.next = d
// 遍历
let p = a
while (p) {
console.log(p.val)
p = p.next
}
// 在c和d中插入e
const e = { val: "e" }
c.next = e
e.next = d
// 删除e
c.next = d
使用数组生成链表并反转:
function createLinkList(arr) {
const length = arr.length;
if (length === 0) throw new Error("arr is empty");
let curNode = {
value: arr[length - 1]
};
if (length === 1) return curNode;
for (let i = length - 2; i >= 0; i--) {
curNode = {
value: arr[i],
next: curNode
};
}
return curNode;
}
const arr = [100, 200, 300, 400];
const listNode = createLinkList(arr);
function reverseLinkList(listNode) {
let preNode = null;
let curNode = null;
let nextNode = listNode;
while (nextNode) {
// 第一个元素删除其next,防止循环引用
if (curNode && !preNode) {
delete curNode.next;
}
// 反转指针
if (curNode && preNode) {
curNode.next = preNode;
}
preNode = curNode;
curNode = nextNode;
nextNode = nextNode.next;
}
// 设置最后一个next
curNode.next = preNode;
return curNode;
}
console.log(reverseLinkList(listNode));
1、LeetCode: 237.删除链表中的节点
const deleteNode = function(node) {
// 当前节点和下个节点赋值相同后,再将下个节点删除
node.val = node.next.val;
node.next = node.next.next;
};
2、LeetCode: 206.反转链表
解题思路
- 反转两个节点:将 n+1 的 next 指向 n
- 反转多个节点:双指针遍历链表,重复上述操作
解题步骤
- 双指针一前一后遍历链表
- 反转双指针
// 1 -> 2 -> 3 -> 4 -> 5 -> null
// 5 -> 4 -> 3 -> 2 -> 1 -> null
// 时间复杂度 O(n) n为链表的长度
// 空间复杂度 O(1)
const reverseList = function(head) {
// 创建一个指针
let p1 = head;
// 创建一个新指针
let p2 = null;
while(p1) {
const temp = p1.next;
p1.next = p2;
p2 = p1;
p1 = temp;
}
return p2;
}
3、LeetCode: 2.两数相加
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
// 时间复杂度 O(n) n 为 l1 l2 链表长度的较大值
// 空间复杂度 O(n) n 为 l1 l2 链表长度的较大值
const addTwoNumbers = function(l1, l2) {
const l3 = new ListNode(0);
let p1 = l1;
let p2 = l2;
let p3 = l3;
let carry = 0;
while(p1 || p2) {
const v1 = p1 ? p1.val : 0;
const v2 = p2 ? p2.val : 0;
const val = v1 + v2 + carry;
// 获取和的十位数上的数
carry = Math.floor(val / 10);
// 获取个位数上的数
p3.next = new ListNode(val % 10);
if(p1) p1 = p1.next;
if(p2) p2 = p2.next;
p3 = p3.next;
}
// 循环结束最后一位是否需要进一
if(carry) {
p3.next = new ListNode(carry);
}
return l3.next;
};
4、LeetCode: 83.删除排序链表中的重复元素
解题思路
- 因为链表是有序的,所以重复元素一定相邻
- 遍历链表,如果发现当前元素和下个元素相同,就删除下个元素值
解题步骤
- 遍历链表,如果发现当前元素和下个元素值相同,就删除下个元素值
- 遍历结束后,返回原链表的头部
// 时间复杂度 O(n) n为链表的长度
// 空间复杂度 O(1)
const deleteDuplicates = function(head) {
let p = head;
// 遍历链表
while(p && p.next) {
// 如果当前节点的值等于下一个节点的值
if(p.val === p.next.val) {
// 删除下一个节点
p.next = p.next.next;
}else {
p = p.next;
}
}
return head;
};
5、LeetCode:141.环形链表
解题思路
- 两个人在圆形操场上的起点同时起跑,速度快的人一定会超过速度慢的人一圈
- 用一快一慢两个指针遍历链表,如果指针能够相逢,那么链表就有圈
解题步骤
- 用一快一慢两个指针遍历链表,如果指针能够相逢,就返回 true
- 遍历结束后,还没有相逢就返回 false
const hasCycle = function(head) {
let p1 = head;
let p2 = head;
while(p1 && p2 && p2.next) {
p1 = p1.next;
p2 = p2.next.next;
if(p1 === p2) return true;
}
return false;
};
6、手写instanceOf
首先我们先了解下原型链有关知识点:
- 原型链的本质是链表
- 原型链上的节点是各种原型对象,比如 Function.prototype、Object.prototype....
- 原型链通过 proto 属性连接各种原型对象
- obj => Object.prototype => null
- func => Function.prototype => Object.prototype => null
- arr => Array.prototype => Object.prototype => null
- 如果 A 沿着原型链能找到 B.prototype,那么 A instanceof B 为 true
- 如果在 A 对象上没有找到 x 属性,那么会沿着原型链找 x 属性
手写instanceOf解法:
- 遍历 A 的原型链,如果找到 B.prototype,返回 true,否则返回 false
const instanceOf = (A, B) => {
let p = A;
while(p) {
if(p === B.prototype) {
return true;
}
p = p.__proto__;
}
return false;
}
console.log(instanceOf([], Array)); // true
console.log(instanceOf({}, Object)); // true
console.log(instanceOf(1, Number)); // true
7、使用链表指针获取JSON 的节点值
const json = {
a: { b: { c: 7 } },
d: { e: 2 }
};
const path = ["a", "b", "c"];
let p = json;
path.forEach(key => {
p = p[key];
})
console.log(p); // 7