【js】-【链表-应用】-学习笔记
声明:本笔记是根据掘金小册总结的,如果要学习更多细节,请移步https://juejin.cn/book/6844733800300150797
1 链表的合并
描述:
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的
输入:
1->2->4,
1->3->4
输出:
1->1->2->3->4->4
思路:
下图中红色箭头表明穿针的过程与方向
- 新建一个头节点,建立一个指针
cur
,作为针- 把比较小的那个节点串起来,指针向前移,当前 cur指针也往前移
- 最后处理没有走完的链表,拼在最后
const mergeTwoLists = function(l1, l2) {
#1 定义头结点,确保链表可以被访问到
let head = new ListNode()
// cur 这里就是咱们那根“针”
let cur = head
// “针”开始在 l1 和 l2 间穿梭了
while(l1 && l2) {
# 2.1如果 l1 的结点值较小,先串起 l1 的结点,l1向前走
if(l1.val<=l2.val) {
cur.next = l1
l1 = l1.next
} else {
#2.2 如果l2 较小时,串起 l2 结点,l2 向前一步
cur.next = l2
l2 = l2.next
}
#3 “针”在串起一个结点后,也会往前一步
cur = cur.next
}
#4 处理链表不等长的情况
cur.next = l1!==null?l1:l2
// 返回起始结点
return head.next
};
2 链表结点的删除
1. 删除所有重复的元素
描述:
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
输入:
1->1->2
输出:1->2
输入:
1->1->2->3->3
输出:1->2->3
const deleteDuplicates = function(head) {
#1 设定 cur 指针,初始位置为链表第一个结点
let cur = head;
#2 遍历链表
while(cur != null && cur.next != null) {
if(cur.val === cur.next.val) {
# 2.1 若当前结点和它后面一个结点值相等(重复),删除靠后的那个结点(去重)
cur.next = cur.next.next;
} else {
# 2.2 若不重复,继续遍历
cur = cur.next;
}
}
return head;
};
2 删除所有含有重复数字的结点
描述:
给定一个排序链表,删除所有含有重复数字的结点,只保留原始链表中 没有重复出现的数字
输入:
1->2->3->3->4->4->5
输出:
1->2->5
输入:
1->1->1->2->3
输出:
2->3
思路:
链表的第一个结点,因为没有前驱结点,导致我们面对它无从下手。这时我们就可以用一个dummy
结点来解决这个问题。
1 定义dummy
结点,指向头结点,cur
从dummy
遍历
2. 遇到一样的,记录val的值,开始循环遍历,把一样的都删除。不一样则跳过
3 返回dummy.next
,即链表的起始节点
const deleteDuplicates = function(head) {
#1 极端情况:0个或1个结点,则不会重复,直接返回
if(!head || !head.next) {
return head
}
#2 dummy 登场,dummy 永远指向头结点
let dummy = new ListNode()
dummy.next = head
#3 cur 从 dummy 开始遍历
let cur = dummy
// 当 cur 的后面有至少两个结点时
while(cur.next && cur.next.next) {
// 对 cur 后面的两个结点进行比较
if(cur.next.val === cur.next.next.val) {
// 若值重复,则记下这个值
let val = cur.next.val
// 反复地排查后面的元素是否存在多次重复该值的情况
while(cur.next && cur.next.val===val) {
// 若有,则删除
cur.next = cur.next.next
}
} else {
// 若不重复,则正常遍历
cur = cur.next
}
}
// 返回链表的起始结点
return dummy.next;
};
dummy
结点,模板代码
const dummy = new ListNode()
// 这里的 head 是链表原有的第一个结点
dummy.next = head
3 删除链表的倒数第 N 个结点(快慢指针)
描述:
给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
给定一个链表:
1->2->3->4->5
, 和n = 2.
当删除了倒数第二个结点后,链表变为1->2->3->5
.
思路:
- 定义dummy结点作为头结点的前驱, 快慢指针都从dummy开始
- 快指针先走n步,
- 快慢指针开始同步走,
- 删除慢指针的后继,即倒数第n个结点
- 返回dummy.next,即头结点
const removeNthFromEnd = function(head, n) {
#1 初始化 dummy 结点,指向头结点
const dummy = new ListNode()
dummy.next = head
#2 初始化快慢指针,均指向dummy
let fast = dummy
let slow = dummy
#3 快指针闷头走 n 步
while(n!==0){
fast = fast.next
n--
}
#4 快慢指针一起走
while(fast.next){
fast = fast.next
slow = slow.next
}
#5 慢指针删除自己的后继结点
slow.next = slow.next.next
// 返回头结点
return dummy.next
};
4.0 链表的反转(多指针)
描述:
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点
输入:
1->2->3->4->5->NULL
输出:5->4->3->2->1->NULL
思路:
- 前驱结点
pre
,当前结点cur
,后继结点next
next
指向pre
,然后pre
和cur
都继续后移- 最后返回
pre
,就是新链表的头结点
const reverseList = function(head) {
#1 初始化前驱结点为 null
let pre = null;
#2 初始化目标结点为头结点
let cur = head;
#3 只要目标结点不为 null,遍历就得继续
while (cur !== null) {
// 记录一下 next 结点
let next = cur.next;
// 反转指针
cur.next = pre;
// pre 往前走一步
pre = cur;
// cur往前走一步
cur = next;
}
// 反转结束后,pre 就会变成新链表的头结点
return pre
};
4.1 局部反转一个链表
描述:
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
输入:
1->2->3->4->5->NULL, m = 2, n = 4
输出:1->4->3->2->5->NULL
思路
- 先定义
dummy
,找到m
的前驱,并保存下来lefthead
- 从
m
作为pre
,cur指向pre.next
开始翻转- 把
leftHead
指向pre
,start
指向cur
// 入参是头结点、m、n
const reverseBetween = function(head, m, n) {
// 定义pre、cur,用leftHead来承接整个区间的前驱结点
let pre,cur,leftHead
#1 dummy结点
const dummy = new ListNode()
dummy.next = head
#2.0 p是一个游标,用于遍历,最初指向 dummy,往前走 m-1 步,走到整个区间的前驱结点处
let p = dummy
for(let i=0;i<m-1;i++){
p = p.next
}
#2.1 缓存这个前驱结点到 leftHead 里
leftHead = p
let start = leftHead.next // start 是反转区间的第一个结点
pre = start// pre 指向start
cur = pre.next // cur 指向 start 的下一个结点
#3 开始重复反转动作
for(let i=m;i<n;i++){
let next = cur.next// 记录一下 next 结点
cur.next = pre // 反转指针
pre = cur// pre 往前走一步
cur = next // cur往前走一步
}
#4 leftHead 的后继结点此时为反转后的区间的第一个结点
leftHead.next = pre
start.next=cur // 将区间内反转后的最后一个结点 next 指向 cur
return dummy.next // dummy.next 永远指向链表头结点
};
5 判断链表是否成环
- 改变结点,
flag
const detectCycle = function(head) {
while(head){
if(head.flag){
return head;
}else{
head.flag = true;
head = head.next;
}
}
return null;
};
2. 可以用map模拟 (个人喜欢,牢记)
const detectCycle = function(head) {
const visited = new Map();
while(head){
if(visited.has(head)){
return head;
}else{
visited.set(head)
head = head.next;
}
}
return null;
};
- 快慢指针
快指针走两步,慢指针走一步
如果相等了就有环
const detectCycle = function(head) {
if(!head) return null;
let slow=head,fast=head;
#1 快指针没到头就继续
while (fast !== null) {
#2 慢指针走一步,快指针走两步
slow = slow.next;
if (fast.next !== null) {
fast = fast.next.next;
} else {
return null;
}
#3 如果快慢指针相遇
if (fast === slow) {
let ptr = head;
while (ptr !== slow) {
ptr = ptr.next;
slow = slow.next;
}
return ptr;
}
}
return null;
};
我们再额外使用一个指针
ptr
。
起始,它指向链表头部
;随后,它和slow
每次向后移动一个位置。最终,它们会在入环点相遇。