⑤ 数据结构之“链表”
function ListNode(val, next) {
this.val = (val===undefined ? 0 : val)
this.next = (next===undefined ? null : next)
}
一、 理论
1. 链表简介

- 多个元素组成的列表
- 元素存储不连续,用next指针连在一起
2. 数组 vs 链表
- 数组:增删非首尾元素需要移动元素
- 链表:增删非首尾元素不需要移动元素,只需要改动元素的next指针
3. js中的链表
- js中没有链表,使用object模拟链表
3.1 链表实现
// linkList
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
3.2 遍历链表
let p = a
while(p) {
console.log(p.val)
p = p.next
}
3.3 插入
const e = {val: 'e' }
c.next = e
e.next = d
3.4 删除
c.next = d
二、刷题
1. 删除链表中的节点(237)
1.1 题目描述
- 请编写一个函数,用于 删除单链表中某个特定节点
- 在设计函数时需要注意,你无法访问链表的头节点 head ,只能直接访问 要被删除的节点
- 题目数据保证需要删除的节点 不是末尾节点
1.2 解题思路
输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
- 无法直接获取被删除节点的上个节点
- 将被删除的节点转移到下个节点
1.3 解题步骤
- 将被删除的节点的值改为下个节点的值
- 删除下个节点
function deleteNode(node) {
node.val = node.next.val
node.next = node.next.next
}
1.4 时间复杂度&空间复杂度
- 时间复杂度:O(1)
- 空间复杂度:O(1)
2. 反转链表(206)
2.1 题目描述
- 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表
2.2 解题思路
输入: 1 -> 2 -> 3 -> 4 -> 5 -> null
输出: 5 -> 4 -> 3 -> 2 -> 1 -> null
- 反转两个节点:将n+1的next指向n
- 反转多个节点:双指针遍历链表
2.3 解题步骤
- 双指针一前一后遍历链表
- 反转双指针
function reverseList(head) {
let p1 = head, p2 = null
while(p1) {
const tmp = p1.next
p1.next = p2
p2 = p1
p1 = tmp
}
return p2
}
2.4 时间复杂度&空间复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
3. 两数相加(2)
3.1 题目描述
- 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字
- 请你将两个数相加,并以相同形式返回一个表示和的链表
- 你可以假设除了数字 0 之外,这两个数都不会以 0 开头
3.2 解题思路
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
- 模拟相加操作
- 需要遍历链表
3.3 解题步骤
- 新建空链表
- 遍历两个被相加的链表,模拟相加操作,将个位数追加到新链表上,将十位数留到下一位去相加
function addTwoNumbers(l1, l2) {
const l3 = new ListNode(0)
let p1 = l1, p2 = l2, 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 = parseInt(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
}
3.4 时间复杂度&空间复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(n)
4. 删除排序链表中的重复元素(83)
4.1 题目描述
- 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次
- 返回同样按升序排列的结果链表
4.2 解题思路
输入:head = [1,1,2]
输出:[1,2]
- 因为链表有序,所以重复元素一定相邻
- 遍历链表,若发现当前元素与下个元素值相同,就删除下个元素值
4.3 解题步骤
- 遍历链表,若发现当前元素与下个元素值相同,就删除下个元素值
- 遍历结束后,返回原链表头部
function deleteDuplicates(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
}
4.4 时间复杂度&空间复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
5. 环形链表(141)
5.1 题目描述
-
给你一个链表的头节点 head ,判断链表中是否有环
-
为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)
-
如果 pos 是 -1,则在该链表中没有环
-
注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况
-
如果链表中存在环,则返回 true
-
否则,返回 false
5.2 解题思路

输入:head = [3,2,0,-4], pos = 1
输出:true
- 两人在操场同起点起跑,速度快的人一定会超过速度慢的人一圈
- 用一快一慢指针遍历链表,若指针相逢则表示链表有圈
5.3 解题步骤
- 用一快一慢指针遍历链表,若指针相逢则返回true
- 遍历结束后,还没相逢则返回false
function hasCycle(head) {
let p1 = head, p2 = head
while(p1 && p2 && p2.next) {
p1 = p1.next
p2 = p2.next.next
if(p1 == p2) return true
}
return false
}
5.4 时间复杂度&空间复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
6. js中的原型链(前端与链表)
6.1 简介
- 原型链的本质是链表
- 原型链上的节点是各种原型对象,比如Function.prototype、Object.prototype......
- 原型链通过
__proto__
属性连接各种原型对象
6.2 原型链
- obj -> Object.prototype -> null
- func -> Function.prototype -> Object.prototype -> null
- arr -> Array.prototype -> Object.prototype -> null
6.3 code part
// proto
const obj = {}
const dunction = () => {}
const arr =[]
6.4 原型链知识点
① 如果A沿着原型链能找到B,则A instanceof B ==> true
obj instanceof Object
func instanceof Function
arr instanceof Array
arr instanceof Object
② 如果在A对象上没有找到x属性,会沿着原型链找x属性
const obj = {}
Object.prototype.x = 'x'
const func = () => {}
Function.prototype.y = 'y'
6.5 面试题
6.5.1 简述instanceof的原理,并用代码实现
- 知识点:如果A沿着原型链能找到B,则A instanceof B ==> true
- 解法:遍历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
}
instanceOf([], Array)
instanceOf({}, Object)
6.5.2 打印
var foo = {}, F = function() {}
Object.prototype.a = 'value a'
Function.prototype.b = 'value b'
console.log(foo.a) // 'value a'
console.log(foo.b) // undefined
console.log(F.a) // 'value a'
console.log(F.b) // 'value b'
- 知识点: 如果在A对象上没有找到x属性,会沿着原型链找x属性
- 解法:明确foo和F变量的原型链,沿着原型链找到a和b的值
7. 使用链表指针获取json的节点值(前端与链表)
// json
const json = {
a: { b: { c: 1 } },
d: { e: 2 }
}
const path = ['a', 'b', 'c']
let p = json
path.forEach(k => p = p[k])
三、总结 -- 技术要点
- 链表里的元素存储不连续,用next指针连在一起
- js中没有链表,使用object模拟链表
- 链表常用操作:修改next、遍历链表
- js中的原型链也是一个链表,沿着
__proto__
走 - 使用链表指针可以获取json的节点值
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)