持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情
Hello,这里是mouche,当然你也可以叫我某车,反正大家都爱这么叫😁
之前我已经在分类刷算法题(一)链表 | 前端er ,已经叨叨叨过一些经典的链表算法题了,我自己也受益于这一系列文章,让我刷题咔咔快
今天再加一篇是因为,想把CodeTop企业题库里面前端的链表题全部搞定
做到链表无忧,面试遇到链表题再也不用害怕啦
一、两两交换链表的交点
🤜题目
- 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)
- 难度:中等
- 这道题其实就是反转链表的扩展题,如果还不会反转链表的话可以先做一下这道题:剑指 Offer II 024. 反转链表 - 力扣(LeetCode) (我上一篇文章也有归类反转链表)
🤜分析过程
- 因为每一次交换肯定都需要连接前继节点,为了一统操作,我们
dummy节点也得出场了 - 如图所示
- 第一步应该为:
cur.next = next.next; - 第二步应该为:
next.next = cur; - 第三步应该为:
prev.next = next;这里的
cur.next = next.next(第一步)必须在next.next = cur(第二步)之前,因为3这个节点是没有指针去指向它的,所以一旦next.next更改,那么就会找不到它
- 第一步应该为:
- 继续分析第二次交换,也就是说,第一次交换之后
prev指向curcur指向cur.nextnext指向变化后的cur.next
🤜解决代码
- 了解了每一步需要干什么,以及怎么变换到下一步,其实我们的代码就已经成型了
var swapPairs = function(head) {
let dummy = new ListNode(); //生成dummy结点
dummy.next = head;
//这里指向哪个结点可以对应图看
let prev = dummy;
let cur = prev.next;
while(cur && cur.next) {
let next = cur.next;
//三步走,我们在第一次交换分析了
cur.next = next.next;
prev.next = next;
next.next = cur;
//我们在第二次交换分析了
prev = cur;
cur = cur.next;
}
return dummy.next
};
二、回文链表
👏题目
- 给你一个单链表的头节点
head,请你判断该链表是否为回文链表。如果是,返回true;否则,返回false。 - 难度;简单
- 虽然它的难度是简单,但是我觉得是反转链表和找到中间结点的结合,这两道题都是经典题,我在上一篇也总结了:中间结点
👏分析过程
- 根据回文的特性:关于中间对称,那我们可以找到中间结点,然后反转后面的链表,然后比对两个链表是否相等即可
- 找到中间结点:采用的是快慢指针,快指针始终比慢指针走快一步,那么当快指针走到能走到的最后一个的时候,慢指针刚好走到中间结点
- 反转链表:上一道题已经使用过了,这里就不赘述了
- 比对: 使用双指针法,依次比较,如果有不相等的直接返回
false,否则返回true
👏解决代码
我们可以依次根据上面三个步骤写出代码
- 找到中间结点:
const findCenter = function(head) {
let fast = head; //快指针
let slow = head; //慢指针
while(fast && fast.next && fast.next.next) {
fast = fast.next.next; //多走一步
slow = slow.next;
}
return slow
}
- 反转链表:
const reverseList = function(head) {
let prev = null;
let cur = head;
while(cur) {
let next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev
};
- 比对链表:
//node2应该传入反转后的链表
const isSame = function(node1, node2) {
let n1 = node1;
let n2 = node2;
while(n2) {
if(n1.val != n2.val) return false //如果有一个不一样则返回false
n1 = n1.next;
n2 = n2.next;
}
return true //都一样就返回true
}
- 总代码:
var isPalindrome = function(head) {
if(!head) return true; //如果为空则一定为回文链表
const center = findCenter(head); //找到中间
const secondFirst = reverseList(center.next); //反转链表,为什么是next可以看一下上面的图
return isSame(head, secondFirst) //比对
};
三、排序链表
🙌题目
- 给你链表的头结点
head,请将其按 升序 排列并返回 排序后的链表 - 难度:中等
- 排序的算法是很多的,这道题我们可以采用分治思想,用分而治之的思想使用归并排序,还可以复习一下 找到中间结点和 合并有序链表 ,也都是经典题🤙 :合并有序链表
🙌分析过程
- 如图,这个过程是一直分割到一个临界点,然后合并它,所以是用递归,那么我们就来分析递归的临界值和递归的每一步要做什么就好
- 临界值:如果所示,应该是分割到剩它一个结点或者空,所以临界值就是
!head || !head.next - 每一步:
- 首先是找到中间节点
- 然后对它进行分割
- 分割完就对左边和右边分别进行排序--递归
- 合并有序链表
🙌解决代码
- 找到中间结点上一道我们已经说过了,就不附代码了
- 合并有序链表
//合并有序链表--采用穿针引线
const mergeLists = function(l1,l2) {
let dummy = new ListNode();
let cur = dummy;
let p1 = l1;
let p2 = l2;
while(p1 && p2) {
if(p1.val < p2.val) { //p1指向的节点小于p2的时候,就把p1穿起来
cur.next = p1;
p1 = p1.next;
}else {
cur.next = p2; //否则就穿p2
p2 = p2.next;
}
cur = cur.next;
}
cur.next = !p1? p2: p1; //连接剩下的
return dummy.next //返回合并后的链表
}
- 总代码
var sortList = function(head) {
//递归临界值
if(!head || !head.next) {
return head;
}
//找中点
let center = findCenter(head);
let second = center.next;
//分割
center.next = null;
//对左边和右边分别进行排序
let left = sortList(head);
let right = sortList(second);
//合并有序链表
return mergeLists(left,right)
};
四、旋转链表
🤝题目
- 给你一个链表的头节点
head,旋转链表,将链表每个节点向右移动k**个位置 - 难度:中等
- 这道题跟前面几道题不太一样,因为前几道都算是经典题目的合成题,这道题更像是开辟了新的思维
🤝分析过程
- 我们先来理解它的题意是怎么去《旋转》
- 设链表长度为
L,由上面图可以分析出- 当移动k<L时,就是实际移动的位数
- 当移动k=L时,就刚好不动,还是它本身(当然,它的整数倍也是一样的道理)
- 当移动k>L时,就是移动k%5的值
🤝解决代码
var rotateRight = function(head, k) {
if(!head || !head.next || !k) return head; //这里主要k等于0的时候也要加进入,测试用例有
let L = 1, cur = head; //L表示长度
//计算出长度
while(cur.next){
cur = cur.next;
L++;
}
//计算出需要切断的地方
let move = L - k % L;
cur.next = head; //看图
//看图
while(move){
cur = cur.next;
move--;
}
let res = cur.next; //看图
//切断
cur.next = null; //看图
return res;
};
- 我在比较难理解的几步画了图解
五、絮絮念
- 🤪还有两道题是二叉树和链表的转换,我打算放到二叉树专题搞定
- 🤤如果你完成了这两篇的链表题,其实就会发现
CodeTop前端链表相关的已经一片绿啦 - 🥳建议先看第一篇,再看这一篇,就比较系统完善
到这里就都完成啦, 觉得还不错的点个赞吧🥰💓