前端er必会算法-链表经典问题&小技巧篇

359 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

原文位于 github仓库-正在起步阶段的前端知识库,其中记录了一名前端初学者的学习日记🤔&学习之路点点滴滴的记录(练手demo🧑‍💻,必会知识点🧐)

欢迎大家来贡献更多“前端er必会知识点”🧑‍🎓/分享更多有意义的demo❤️!(请给这个年幼的小仓库更丰富的内容吧🥺🥺🥺)

本篇汇总一下链表题型中双指针的妙用&环形链表的经典问题~

【1】双指针的妙用

【medium】29.删除链表的倒数第 N 个结点

两个要点——

  • 虚拟头结点
  • 双指针(快指针先走一步~)
 var removeNthFromEnd = function(head, n) {
     let dummy = new ListNode(0, head);
     let slow = dummy;
     let fast = dummy;
     for(let i = 0; i < n; i++){
         fast = fast.next;
     }
     while(fast !== null && fast.next !== null){
         fast = fast.next;
         slow = slow.next;
     }
     slow.next = slow.next.next;
     return dummy.next;
 };

206.反转链表

  • 双指针法

这题的双指针写法相当经典!

在很多其他题型的解决方案种也有体现!(比如回文链表)

 var reverseList = function(head) {
     let pre = null;
     let cur = head;
     while(cur !== null){
         let temp = cur.next;
         cur.next = pre;
         pre = cur;
         cur = temp;
     }
     return pre;
 };

分享一篇GIF图解,当初我就是看着这篇的GIF明白的😄

  • 递归法速解

还可以使用递归的方法秒杀本题~

 var reverseList = function(head) {
     // “递”的过程
     if(head === null || head.next === null){
         return head;
     }
     let newHead = reverseList(head.next);// 把head.next这个子问题传进去 
     // “归”的过程
     head.next.next = head;
     head.next = null;
     return newHead;
 };

【medium】92.反转链表 II

好题!局部反转!

要点:保存断开连接的边缘,用于连接反转过后得到的头节点

 var reverseBetween = function(head, left, right) {
     let dummy = new ListNode(0,head);
     let p = dummy;// 用于遍历
     for(let i = 0; i < left - 1; i++){
         p = p.next;
     }
     let leftHead = p;// 保存断开的边缘
     let start = leftHead.next;// 保存开始反转的边界
     // 设置双指针
     let pre = start;
     let cur = pre.next;
     for(let j = left; j < right; j++){
         // 反转局部链表
         let temp = cur.next;
         cur.next = pre;
         pre = cur;
         cur = temp;
     }
     leftHead.next = pre;// 连接最开始(断链的边缘)和局部反转链表反转过后得到的头节点
     start.next = cur;// 连接反转后的链表和后面那段链表
     return dummy.next;
 };

【2】经典环形链表问题

141. 环形链表

快慢指针的经典(也是很简单的一个)实践~

 var hasCycle = function(head) {
     let slow = head;
     let fast = head;
     while(fast !== null && fast.next !== null){
         slow = slow.next;
         fast = fast.next.next;
         if(slow === fast){
             return true;
         }  
     }
     return false;
 };

【medium】142. 环形链表 II

超级经典的面试题,尽量对其中的数学原理多了解些!面试时候可以说出点东西最好!

我写的题解

推荐看的图文并茂的题解(当时我就是看这个懂的!)

简单说一下这题的数学原理——

首先 设链表长度为 a + b(环内结点个数)

  • 【1】slow指针a+nb步之后一定会到入口;
  • 【2】二者第一次相遇时慢指针已经走了nb步;

    • 数学推导得来——fast走了2*slow 且第一次相遇时fast比slow快了 b*n 步 联立一下:
    • fast = 2 * slow ; fast = slow + n*b; —— slow = n*b

所以要让slow在第一次与fast相遇后再走a步停下来(满足刚好走完一整圈链表的长度)

这里建议看一下大佬们的图解XD 就很好理解了!

 var detectCycle = function(head) {
     let slow = head;
     let fast = head;
     // 01 让两个指针第一次相交 此时slow指针走了n*b步
     while(true){
         if(fast === null || fast.next === null){
             return null;
         }
         slow = slow.next;
         fast = fast.next.next;
         if(slow === fast){
             break;
         }
     }
     // 02 换新的结点从头结点开始丈量a步 保证slow指针一共走了a+n*b步
     let newFast = head;
     while(slow !== newFast){
         newFast = newFast.next;
         slow = slow.next;
     }
     // 03 返回slow指向的结点 一定刚好走完一整圈
     return slow;
 }