🧑💻JavaScript算法与数据结构-HowieCong
务必要熟悉JavaScript使用再来学!
一、快慢指针——删除链表的倒数第N个结点
- 快慢指针时两个一前一后地指针,两个指针向同一个方向走,一个快,一个慢
原题:
给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2
当删除了倒数第二个结点后,链表变为 1->2->3->5
说明: 给定的 n 保证是有效的。
(1)思路分析
- dummy结点可以帮助我们处理掉头结点地边界问题
const dummy = new ListNode()
// 这里地head是链表原有的第一个结点
dummy.next = head
-
因为遍历不可能从后往前走,可以转换为正数的第len-n+1个,len是链表总长度
-
遍历两遍:
-
第一遍:设置一个变量为count=0,每遍历到一个不为空的结点,count就加1,每遍历一个不为空的结点,count就加1,一直遍历到链表结束为止,得到链表的总长度len
-
根据这个总长度,咱们就可以算出倒数第n个到底是正数的第几个(M=len - n + 1),遍历到M-1个结点的时候停下来,执行删除操作
-
-
步骤:
-
首先两个指针slow和fast,全部指向链表的起始位——dummy结点
-
快指针先出发,在第n个结点处打住,这里n=2
-
快慢指针一起前进,当快指针前进到最后一个结点处时,两个指针一起停下来
-
此时慢指针所指的位置,就是倒数第n个结点前的一个结点
-
基于这个结点来做删除
- 总结:
-
求长度
-
做减法,找定位
(2)编码实现
const removeNthFromEnd = function(head,n){
// 初始化dummy结点
const dummy = new ListNode()
// dummy指向头结点
dummy.next = head
// 初始化快慢指针,均指向dummy
let fast = dummy
let slow = dummy
// 快指针先走n
while(n!==0){
fast = fast.next
n--
}
// 快慢指针一起走
while(fast.next){
fast = fast.next
slow = slow.next
}
// 慢指针删除自己的后续结点
slow.next = slow.next.next
// 返回头结点
return dummy.next
}
二、多指针法——链表的反转
原题:
定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
(1)思路分析
-
处理链表的本质,是处理链表结点之间的指针关系
-
需要用到三个指针,它们分别指向目标结点(cur)、目标结点的前驱结点(pre)、目标结点的后继结点(next)
-
需要一个简单的
cur.next = pre,就做到了 next 指针的反转
(2)编码实现
const reverseList = function(head){
// 初始化前驱结点为null
let pre = null;
// 初始化目标结点为头结点
let cur = head;
// 只要目标结点不为null,遍历就得继续
while(cur !== null){
// 记录一下next结点
let next = cur.next;
// 反转指针
cur.next = pre;
// pre 往前走一步
pre = cur;
// cur 往前一步
cur = next;
}
// 反转结束后,pre会变成新链表的头结点
return pre
}
三、局部反转一个链表
原题:
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转
说明: 1 ≤ m ≤ n ≤ 链表长度
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
(1)思路分析
-
4指3,3指2,这都没问题,关键在于,如何让1指向4、让2指向5
-
在单纯的重复“逆序”这个动作之外,还需要对被逆序的区间前后的两个结点做额外的处理
-
遍历链表的顺序是从前往后遍历,避免结点1和结点2随着遍历向后推进被遗失,提前把1结点缓存下来
-
结点5就没有这么麻烦了:随着遍历的进行,当我们完成了结点4的指针反转后,此时 cur 指针就恰好指在结点5上
-
直接将结点2的 next 指针指向 cur、将结点1的 next 指针指向 pre
(2)编码实现
const reverseBetween= function(head,m,n){
// 定义pre、cur,用leftHead来承接整个区间的前驱结点
let pre,cur,leftHead
// 用dummy结点
const dummy = new ListNode()
// dummy后续结点是头结点
dummy.next = head
// p是一个游标,用于遍历,最初指向dummy
let p = dummy
// p往前走m-1步,走到整个区间的前驱结点初
for(let i = 0;i < m - 1;i++){
p = p.next
}
// 缓存这个前驱节点到leftHead里
leftHead = p
// start是反转区间的第一个结点
let start = leftHead.next
// pre指向start
pre = start
// cur指向start的下一个结点
cur = pre.next
// 开始重复反转动作
for(let i = m;i < n;i++){
let next = cur.next
cur.next = pre
pre = cur
cur = next
}
// leftHead的后续结点此时为反转后的区间的第一个结点
leftHead.next = pre
// 将区间内反转后最后一个结点next指向cur
start.next = cur
// dummy.next 永远指向链表头结点
return dummy.next
};
❓其他
1. 疑问与作者HowieCong声明
-
如有疑问、出错的知识,请及时点击下方链接添加作者HowieCong的其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
-
若想让作者更新哪些方面的技术文章或补充更多知识在这篇文章,请及时点击下方链接添加里面其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
-
声明:作者HowieCong目前只是一个前端开发小菜鸟,写文章的初衷只是全面提高自身能力和见识;如果对此篇文章喜欢或能帮助到你,麻烦给作者HowieCong点个关注/给这篇文章点个赞/收藏这篇文章/在评论区留下你的想法吧,欢迎大家来交流!