搞定链表相关算法 | 前端er

204 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情

Hello,这里是mouche,当然你也可以叫我某车,反正大家都爱这么叫😁

之前我已经在分类刷算法题(一)链表 | 前端er ,已经叨叨叨过一些经典链表算法题了,我自己也受益于这一系列文章,让我刷题咔咔快

今天再加一篇是因为,想把CodeTop企业题库里面前端的链表题全部搞定

做到链表无忧,面试遇到链表题再也不用害怕啦

一、两两交换链表的交点

🤜题目

24. 两两交换链表中的节点 - 力扣(LeetCode)

  • 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)
  • 难度:中等
  • 这道题其实就是反转链表的扩展题,如果还不会反转链表的话可以先做一下这道题:剑指 Offer II 024. 反转链表 - 力扣(LeetCode) (我上一篇文章也有归类反转链表

🤜分析过程

  • 因为每一次交换肯定都需要连接前继节点,为了一统操作,我们dummy节点也得出场了
  • 如图所示
    • 第一步应该为: cur.next = next.next;
    • 第二步应该为: next.next = cur;
    • 第三步应该为: prev.next = next; image.png

      这里的 cur.next = next.next(第一步)必须在next.next = cur(第二步)之前,因为3这个节点是没有指针去指向它的,所以一旦next.next更改,那么就会找不到它

  • 继续分析第二次交换,也就是说,第一次交换之后
    • prev指向cur
    • cur指向cur.next
    • next指向变化后的cur.next image.png

🤜解决代码

  • 了解了每一步需要干什么,以及怎么变换到下一步,其实我们的代码就已经成型了
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
};

二、回文链表

👏题目

234. 回文链表 - 力扣(LeetCode)

  • 给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
  • 难度;简单
  • 虽然它的难度是简单,但是我觉得是反转链表找到中间结点结合,这两道题都是经典题,我在上一篇也总结了:中间结点

👏分析过程

  • 根据回文的特性:关于中间对称,那我们可以找到中间结点,然后反转后面的链表,然后比对两个链表是否相等即可
  • 找到中间结点:采用的是快慢指针,快指针始终比慢指针走快一步,那么当快指针走到能走到的最后一个的时候,慢指针刚好走到中间结点 image.png
  • 反转链表:上一道题已经使用过了,这里就不赘述了
  • 比对: 使用双指针法,依次比较,如果有不相等的直接返回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) //比对
};

三、排序链表

🙌题目

148. 排序链表 - 力扣(LeetCode)

  • 给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 
  • 难度:中等
  • 排序的算法是很多的,这道题我们可以采用分治思想,用分而治之的思想使用归并排序,还可以复习一下 找到中间结点合并有序链表 ,也都是经典题🤙 :合并有序链表

🙌分析过程

  • 如图,这个过程是一直分割到一个临界点,然后合并它,所以是用递归,那么我们就来分析递归的临界值递归的每一步要做什么就好
  • 临界值:如果所示,应该是分割到剩它一个结点或者空,所以临界值就是!head || !head.next
  • 每一步
    • 首先是找到中间节点
    • 然后对它进行分割
    • 分割完就对左边和右边分别进行排序--递归
    • 合并有序链表 image.png

🙌解决代码

  • 找到中间结点上一道我们已经说过了,就不附代码了
  • 合并有序链表
//合并有序链表--采用穿针引线
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)
};

四、旋转链表

🤝题目

61. 旋转链表 - 力扣(LeetCode)

  • 给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k **个位置
  • 难度:中等
  • 这道题跟前面几道题不太一样,因为前几道都算是经典题目的合成题,这道题更像是开辟了新的思维

🤝分析过程

  • 我们先来理解它的题意是怎么去《旋转》

image.pngimage.png

  • 设链表长度为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;
};
  • 我在比较难理解的几步画了图解 image.png

五、絮絮念

  • 🤪还有两道题是二叉树和链表的转换,我打算放到二叉树专题搞定
  • 🤤如果你完成了这两篇的链表题,其实就会发现CodeTop前端链表相关的已经一片绿啦
  • 🥳建议先看第一篇,再看这一篇,就比较系统完善

到这里就都完成啦, 觉得还不错的点个赞吧🥰💓