本文已参加「新人创作礼」活动,一起开启掘金创作之路。
进入正文
上一遍文章一节介绍了链表的基础知识,不了解的可以看上一篇文章。俗话说光说不练假把式,这一篇我们与实际相结合,来个leecode链表题实战
141. 环形链表
首先讲一下环形链表
如何判断是否是循环链表:首先设置一个快指针(走两步),一个慢指针(走一步),两个这个指针相遇时,这个链表就是环形链表,否则就不是
如下图
代码
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {boolean}
*/
var hasCycle = function(head) {
//首先判断传入是否有值,如果没有值,直接返回false
if(!head) return false
//设置一个快指针,一个慢指针,如果两个指针有相等的时候,那么这个链表必有环
let slow=head,fast=head;
while(fast.next&&fast.next.next){
//慢指针走一步
slow=slow.next;
//快指针走两步
fast=fast.next.next;
//判断两个指针是否相等
if(slow===fast){
return true
}
}
return false
};
142. 环形链表 II
此题需要返回链表开始入环的第一个节点,那如何获取入环的第一个节点呢?就是在快慢指针相等的情况下,头指针和慢指针一起移动,两个指针相遇的节点,就是入环的第一个节点
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function(head) {
if(!head) return null;
let slow=head,fast=head;
while(fast.next&&fast.next.next){
slow=slow.next;
fast=fast.next.next;
if(fast===slow){
//当快慢指针相等的时候,头节点到入环节点的距离和慢指针到入环节点的距离相等
let cur=head;
while(slow!==cur){
slow=slow.next;
cur=cur.next
}
return cur
}
}
return null
};
206. 反转链表
这题求的是反转之后的链表,比较简单,就是相当于修改每个节点指针,让它指向上一个。
/**
* 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) {
if(!head) return null;
//创建个虚拟指针,一个指向头节点的指针
let pre=null,cur=head;
while(cur){
//下面代码拆分
// let next=cur.next; 将cur的指向赋值,防止之后数据丢失
// cur.next=pre; 将cur的指向改为上一个节点,反转脸变
// pre=cur; 将上一个节点向后走一步
// cur=next; 将当前节点向后走一步,然后继续反转链表
[cur.next,pre,cur]=[pre,cur,cur.next]
}
return pre
};
92. 反转链表 II
这个题和上一个一样,只不过多了一个区间值,所以我们需要先找到这个区间值,再做上面反转的操作
/**
* 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} left
* @param {number} right
* @return {ListNode}
*/
var reverseBetween = function(head, left, right) {
if(!head) return null;
//创建两个虚拟指针,以及要反转的节点个数
let ret=new ListNode(-1,head),temp=ret,count=right-left+1;
//将指针移动到第一个需要反转前一个的位置
while(--left){
ret=ret.next
}
//传入反转的节点
ret.next=reverse(ret.next,count);
return temp.next
};
function reverse(head,count){
let pre=null,cur=head;
//在左右区间反转
while(count--){
[cur.next,pre,cur]=[pre,cur,cur.next]
}
//让需要反转的最开始第一个节点(反转后是最后一个节点),和剩下的数据链接上 head是2,cur是5 2->5
head.next=cur;
return pre
}
25. K 个一组翻转链表
这题的意思也是反转链表,但是会给你个规定的值k,比如k等于2,就是两两反转,等于3就是三个反转
所以步骤是
1、先循环,在k个值内反转链表
- 如果长度不够k个,那么直接返回
- 如果够k个,就进行反转 2、然后循环移动k个,下一组k反转
/**
* 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) return null;
//创建两个虚拟节点
let ret=new ListNode(-1,head),temp=ret;
while(ret){
//传节点和k进行反转
ret.next=reverse(ret.next,k);
//因为k个节点已经处理过;所以ret往后移动k个
for(let i =0;i<k&&ret;i++){
ret=ret.next
}
//如果ret为空 中断循环
if(!ret) break
}
return temp.next
};
function reverse(head,k){
let pre=head,cur=head,con=k;
//先循环k次看节点够不够k个
while(--k&&pre){
pre=pre.next
}
//如果pre为空,说明节点不够k个,直接返回
if(!pre) return head;
//反转k个节点
while(con--){
[cur.next,pre,cur]=[pre,cur,cur.next]
}
head.next=cur;
return pre
}
19. 删除链表的倒数第 N 个结点
上一章说过,删除节点就是将需要删除节点的前一歌的指针,指向删除节点后一个,此题要求删除倒数n个节点,所以我们需要先设置一个节点,将它移动到删除位置之前
所以步骤是
- 先创建一个节点a,让它移动n步;
- 然后创建一个虚拟头节点b,指针指向头节点(将b设置为虚拟头节点是防止删除的节点时头节点)
- a和b同时移动,当a为null时,说明b节点在删除节点的前一位
- 修改b节点的指针指向
/**
* 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) {
if(!head) return null;
//创建一个虚拟头节点
let ret=new ListNode(-1,head),pre=ret,cur=head;
//复制的头节点往后移动n步->3
while(n--){
cur=cur.next
}
//当cur不为空时,虚拟头节点和头节点一起向后移动,当cur为空,说明虚拟头节点在删除节点的前一个
while(cur){
pre=pre.next;
cur=cur.next
}
//将虚拟节点的指针指向虚拟节点的下下个
pre.next=pre.next.next;
return ret.next
};
83. 删除排序链表中的重复元素
因为链表是升序的,所以只需要循环两个节点互相比较值
步骤:
- 设置两个节点,一个在头节点a,另一个在头节点下一个的位置b
- 因为b节点在后,所以退出循环只需要b为空
- 判断两个节点值是否相等
- 当有重复元素时,
- a节点的next指向b节点的next
- 然后a,b节点向后移动,进入下一次循环
- 当没有重复元素时,a和b向后移动,进入下一次循环
/**
* 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 deleteDuplicates = function(head) {
if(!head) return null;
//设置两个节点方便比较
let slow=head,fast=head.next;
//当后一个节点不为空,一直循环判断是否有重复元素
while(fast){
//当有重复元素的时候
if(slow.val===fast.val){
//慢节点的下一个指向快节点的下一个,删除快节点
slow.next=fast.next;
}else{
//当无重复元素,慢节点向后移动继续比较
slow=slow.next;
}
//快节点无论是否有重复元素都需要向后移动
//1、有重复元素时,删除了重复元素所以快节点需要向后移动,与慢节点再比较
//2、当没有重复元素,快慢节点后会向后移动进行新比较
fast=fast.next
}
return head
};
82. 删除排序链表中的重复元素 II
这题要求删除所有的重复元素,所以还是需要两个节点比较
- 创建两个节点,一个虚拟头节点a,一个头节点b,进入循环,b为空,退出循环
- 如果a和b的下一个节点的值不相等,那么同时向后移动(比较下一个节点是因为防止节点a移动到重复元素)
- 如果相等,那么循环b节点向后移动,直到两个节点下一个值不想等,退出当前循环
- 修改a节点的指针指向b节点的下一个
- b节点向后移动进入新一轮循环
/**
* 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 deleteDuplicates = function(head) {
if(!head) return null;
//创建虚拟头节点
let ret=new ListNode(-1,head),pre=ret,cur=head;
//如果真实节点为空,说明链表走到了末尾
while(cur&&cur.next){
//如果虚拟节点的下一个和真实节点的下一个值不相等,两个节点一起向后移动
if(pre.next.val!==cur.next.val){
cur=cur.next;
pre=pre.next
}else{
//如果相等,让cur向后移动,直到两个值不相等退出循环
while(cur&&cur.next&&pre.next.val===cur.next.val){
cur=cur.next
}
//pre节点的指针指向cur节点的指针指向,删除重复的节点
pre.next=cur.next;
//cur向后移动进行下一轮比较
cur=cur.next
}
}
return ret.next
};
86. 分隔链表
给定一个特定值x,比它小的在左边,大的放右边,且不能修改节点位置。
- 所以我们可以新建两个空链表,一个放小的,一个放大的
- 然后将两个链表拼接,就得到了分隔的链表
/**
* 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} x
* @return {ListNode}
*/
var partition = function(head, x) {
if(!head) return null;
//设置两个链表,一个存放比x小的,剩余存放到另一个
let smallList=new ListNode(),bigList=new ListNode(),cur=head;
//两个俩表分别设置两个指针
let smallNode=smallList,bigNode=bigList;
while(cur){
//如果当前的值大于等于x;将当前节点放进大的链表里,并且将小链表的next为null,因为赋值后的next会带着原来指向
if(cur.val>=x){
bigNode.next=cur;
bigNode=bigNode.next
smallNode.next=null
}else{
//如果当前的值大于等于x;将当前节点放进小的链表里
smallNode.next=cur;
smallNode=smallNode.next
bigNode.next=null
}
cur=cur.next
}
//两个链表拼接
smallNode.next=bigList.next
return smallList.next
};
138. 复制带随机指针的链表
这题的难点在于随机指针
- 我们可以在每个节点后面放一个复制的节点
- 修改原来节点和复制节点的指针和随机指针
- 然后将两个链表拆分
此题的图比较难画,所以代码有详细注释,如果还是不太明白的我之后再出图吧😄
/**
* // Definition for a Node.
* function Node(val, next, random) {
* this.val = val;
* this.next = next;
* this.random = random;
* };
*/
/**
* @param {Node} head
* @return {Node}
*/
var copyRandomList = function(head) {
if(!head) return null;
//创建一个节点
let real=head,clone;
while(real){
//创建一个克隆节点,放在当前节点的后面
clone=new ListNode(real.val);
clone.next=real.next;
//复制当前节点的随机指针
clone.random=real.random;
//修改当前节点指向,修改完的head 应该是 原来的节点->复制的节点->原来的节点->复制的...->null
real.next=clone;
//当前节点节点向后移动进入下一次复制
real=clone.next
}
//将clone重新复制头节点的下一个。也就是复制的第一个节点
clone=head.next;
while(clone){
//修改复制节点的random指针,目前是指向原有节点,设置后指向复制节点(复制节点在原有节点后面)
clone.random&&(clone.random=clone.random.next);
//隔一位移动,修改的都是克隆的节点
(clone=clone.next)&&(clone=clone.next)
}
real=clone=head.next;
while(clone.next){
//修正真实节点和克隆节点,拆分两个链表
head.next=head.next.next;
clone.next=clone.next.next;
//头节点和克隆节点向后移动, 继续拆分
head=head.next;
clone=clone.next
}
//此时后一个真实节点指针还指向克隆节点,需要断开
head.next=null
return real
};
707. 设计链表
恭喜你看到这里🎉,终于到了链表的最后一题,设计链表。来解释一下题目要求的方法
MyLinkedList linkedList = new MyLinkedList(); //定义链表
linkedList.addAtHead(1); //在链表头添加值
linkedList.addAtTail(3); //在链表尾添加值
linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3 在下标位置插入节点
linkedList.get(1); //返回2 获取传入位置的节点值
linkedList.deleteAtIndex(1); //现在链表是1-> 3 删除下标位置的节点值
linkedList.get(1); //返回3 获取传入位置的节点值
经过上面题的训练,相信你对链表已经运用的炉火纯青啦,此题应该难不倒你,下面代码有详细注释,如果还是有疑问的地方评论区留言哦😯
function listNode(val,next){
this.val=val; //数据域
this.next=!next?null:next //指针域
}
var MyLinkedList = function() {
this.head=null; //头节点
this.tail=null; //尾节点
this.size=0 //链表长度
};
MyLinkedList.prototype.getNode=function(index){
//判断index是否超出边界
if(index<0||index>=this.size) return -1;
let current=this.head;
while(index--){
current=current.next
}
return current
}
/**
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.get = function(index) {
//判断index是否超出边界
if(index<0||index>=this.size) return -1;
return this.getNode(index).val;
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function(val) {
//建立一个节点并且next指向head
let node=new listNode(val,this.head);
this.head=node;
//判断最开始尾节点为空的情况,说明此链表只有一个节点,所以尾节点和头节点相同
if(!this.tail){
this.tail=this.head
}
this.size++
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function(val) {
let node=new listNode(val);
this.size++
//如果尾节点存在,那么先将添加的节点关联到尾节点,然后尾节点更新
if(this.tail){
this.tail.next=node; //添加节点关联尾节点
this.tail=this.tail.next; //更新尾节点
return
}
//尾节点不存在,说明头尾节点都为空
this.head=node;
this.tail=node
};
/**
* @param {number} index
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function(index, val) {
//判断边界
if(index>this.size) return;
//如果index<=0 相当于直接在头添加
if(index<=0){
this.addAtHead(val);
return
}
//相当于直接在尾添加
if(index===this.size){
this.addAtTail(val)
return
}
//获取添加位置的上一个节点
let prev=this.getNode(index-1);
let node=new listNode(val);
//将要添加的节点指向上一个节点的指针指向
node.next=prev.next;
//将上一个节点的指针指向要添加的节点
prev.next=node
this.size++
};
/**
* @param {number} index
* @return {void}
*/
MyLinkedList.prototype.deleteAtIndex = function(index) {
//判断边界
if(index<0||index>=this.size) return;
//在头删除
if(index===0){
this.head=this.head.next;
//该链表只有一个节点,头尾相等
if(index===this.size-1){
this.tail=this.head;
}
this.size--
return
}
//获取删除位置的前一个节点
let prev=this.getNode(index-1);
//删除节点
prev.next=prev.next.next;
//判断删除尾节点的情况,需要更新尾节点
if(index===this.size-1){
this.tail=prev
}
this.size--
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* var obj = new MyLinkedList()
* var param_1 = obj.get(index)
* obj.addAtHead(val)
* obj.addAtTail(val)
* obj.addAtIndex(index,val)
* obj.deleteAtIndex(index)
*/
链表的基础:前端算法之路-链表