文章目录
- [链表专题:JS 实现原理与高频算法题总结](#链表专题:JS 实现原理与高频算法题总结)
- 1.使用js语法写链表的注意点
-
- 1.1链表的结构体
- [1.2 使用js连成一个链表](#1.2 使用js连成一个链表)
- [1.3 头节点 head 的含义](#1.3 头节点 head 的含义)
- [1.4 JS 中没有指针,但有对象引用](#1.4 JS 中没有指针,但有对象引用)
- [1.5 js中变量保存的是节点引用,不是节点副本](#1.5 js中变量保存的是节点引用,不是节点副本)
- [1.6 创建一个链表](#1.6 创建一个链表)
- [1.7 链表题常见注意点:节点比较、dummy 虚拟头节点](#1.7 链表题常见注意点:节点比较、dummy 虚拟头节点)
- 2.链表高频题实战
-
- [2.1 力扣 160:相交链表](#2.1 力扣 160:相交链表)
- [2.2 力扣 206:反转链表](#2.2 力扣 206:反转链表)
- [2.3 力扣 21:合并两个有序链表](#2.3 力扣 21:合并两个有序链表)
- [2.4 力扣 19:删除链表的倒数第 N 个节点](#2.4 力扣 19:删除链表的倒数第 N 个节点)
- [2.5 力扣 25:K 个一组翻转链表](#2.5 力扣 25:K 个一组翻转链表)
链表专题:JS 实现原理与高频算法题总结
1.使用js语法写链表的注意点
1.1链表的结构体
在 c 语言中,使用结构体 struct 实现
c
struct ListNode {
int val;
struct ListNode* next;
};
在 js 中,链表节点其实就是对象
JS 里的链表节点 = 一个带 val 和 next 的对象
javascript
function ListNode(val, next) {
this.val = (val===undefined ? 0 : val)
this.next = (next===undefined ? null : next)
}
- 如果不写 next=null,默认就是 undefined
其中的一个节点
javascript
const node1 = new ListNode(1)
console.log(node1)
{ val: 1, next: null }
1.2 使用js连成一个链表
javascript
const node1 = new ListNode(1)
const node2 = new ListNode(2)
const node3 = new ListNode(3)
node1.next = node2
node2.next = node3
javascript
node1 -> node2 -> node3 -> null
此时打印 node1,也就是表头
javascript
console.log(node1)
ListNode {
val: 1,
next: ListNode { val: 2, next: ListNode { val: 3, next: null } }
}
可以想象简化为,ListNode表示的是链表节点类的实例
{
val: 1,
next: {
val: 2,
next: {
val: 3,
next: null
}
}
}
1.3 头节点 head 的含义
head 是链表入口,不一定永远是原来的第一个节点。head 本身只是一个变量,它保存的是第一个节点对象的引用,并不是整条链表本身。
javascript
const head = node1
//如果头结点要换来换去就用 let
1.4 JS 中没有指针,但有对象引用
let p = head
//遍历链表
let p = head
while (p !== null) {
console.log(p.val)
p = p.next
}
1.5 js中变量保存的是节点引用,不是节点副本
let p = head
指的是 p 和 head 都指向都一个节点对象
javascript
// 链表是 1-2-3
// node1 是头结点
let p=node1;
p=p.next;
console.log(p.val);
console.log(node1.val);
输出结果:
2
1
修改了节点的 next,才会改变链表
p.next=null
//直接从头结点那里断开了
1.6 创建一个链表
javascript
function ListNode(val, next) {
this.val = (val===undefined ? 0 : val)
this.next = (next===undefined ? null : next)
}
const head = new ListNode(1)
head.next = new ListNode(2)
head.next.next = new ListNode(3)
1.7 链表题常见注意点:节点比较、dummy 虚拟头节点
比较节点要用 ===,这是比较是否是同一个节点对象,而不是比较值
多用 dummy 虚拟头节点
javascript
const dummy = new ListNode(0)
let cur = dummy
return dummy.next
好处是用老判断"头节点是不是空""第一个节点怎么连"。
2.链表高频题实战
2.1 力扣 160:相交链表
javascript
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getIntersectionNode = function(headA, headB) {
// 得到 A 链表的长度 时间复杂度是 o(n)
// 得到 B 链表的长度 时间复杂度是 o(n)
// 做差得到长度差,然后再一起移动,直到一个走到空还是没找到,即不存在,否则仍然存在
// 整体的时间复杂度是 o(n)
let tempA=headA;
let tempB=headB;
let lengthA=0;
let lengthB=0;
let chazhi=0;
let pA=headA;
let pB=headB;
while(tempA!==null)
{
lengthA++;
tempA=tempA.next;
}
while(tempB!==null)
{
lengthB++;
tempB=tempB.next;
}
console.log(lengthA);
if(lengthA>lengthB)
{
chazhi=lengthA-lengthB;
while(chazhi--)
{
pA=pA.next;
}
while(pB!==null)
{
if(pA===pB) return pA;
else{
pA=pA.next;
pB=pB.next;
}
}
}else
{
chazhi=lengthB-lengthA;
while(chazhi--)
{
pB=pB.next;
}
while(pA!==null)
{
if(pA===pB) return pA;
else{
pA=pA.next;
pB=pB.next;
}
}
}
return null;
};
2.2 力扣 206:反转链表
javascript
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
// 定义一个 cur 定义一个 pre pre 指向 null
//首先定义了一个 pre节点,值是链表的第一个节点,指向了 null
// 首先得排除一下这个链表有没有两个节点
if(head==null||head.next==null) return head;
let pre=head;
let cur=head.next;
//得把头结点断开,否则双向链表了
head.next=null;
while(cur!==null)
{
let p=cur.next;
cur.next=pre;
pre=cur;
cur=p;
}
return pre;
};
2.3 力扣 21:合并两个有序链表
javascript
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/
var mergeTwoLists = function(list1, list2) {
//如果两个链表中有空的直接返回另一个即可
if(list1===null)return list2;
if(list2===null)return list1;
//创建一个虚拟的头节点 dummy,两两比较插入
let dummy=new ListNode(0,null);
let p=dummy;
let p1=list1;
let p2=list2;
while(p1!==null&&p2!==null)
{
if(p1.val<=p2.val)
{
p.next=p1;
p=p.next;
p1=p1.next;
}else
{
p.next=p2;
p=p.next;
p2=p2.next;
}
}
//此时有一整条链表已经加入到了新链表中,将剩余部分加回
while(p1!=null)
{
p.next=p1;
p=p.next;
p1=p1.next;
}
while(p2!=null)
{
p.next=p2;
p=p.next;
p2=p2.next;
}
return dummy.next;
};
2.4 力扣 19:删除链表的倒数第 N 个节点
javascript
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
//一个 o(n)的方法,利用两个指针,一个先走 n 步,然后一起移动,先走的指向空之后,后走的那个指向的就是倒数第 n 个节点
let tempN=n;
let fast=head;
let slow=head;
let pre=undefined;
while(tempN--)
{
fast=fast.next;
}
while(fast!=null)
{
fast=fast.next;
pre=slow;
slow=slow.next;
}
// 有可能要删除的结点就是倒数第一个节点,所以要维护一个pre
// 如果要删除的是第一个节点,head=head.next
if(slow===head)
{
head=head.next
return head;
}
if(slow.next==null)
{
pre.next=null;
}else
{
pre.next=slow.next;
}
return head;
};
2.5 力扣 25:K 个一组翻转链表

javascript
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} k
* @return {ListNode}
*/
var reverseKGroup = function(head, k) {
//处理一些特殊的情况
if (head === null || head.next === null) return head;
// 创建一个虚拟头结点
let dummy=new ListNode(0,null);
dummy.next=head;
let end=dummy.next;
let num=1;
//每次要连接的头和尾
let linkhead=dummy;
let linktail=null;
let start=null;
while(end!==null)
{
// 翻转开始的记录
if(num===1) start=end;
//什么时候要翻转?
if(num===k)
{
//翻转要开始的地方
//记录linktail
linktail=end.next;
//断开链表
end.next=null;
// 翻转从 start 开始,从当前的 end 结束
let pre=null;
let cur=start;
while(cur!==null)
{
let p=cur.next;
cur.next=pre;
pre=cur;
cur=p;
}
//翻转成功 pre 指向了翻转后的尾部
linkhead.next=end;
start.next=linktail;
// 更新 linkhead
linkhead=start;
//更新 start 和 end
start=linktail; //更不更新都行
end=linktail;
//更新 num
num=1;
continue;
}
num++;
end=end.next;
}
return dummy.next;
};