• 【js】-【链表-应用】-学习笔记


    声明:本笔记是根据掘金小册总结的,如果要学习更多细节,请移步https://juejin.cn/book/6844733800300150797

    1 链表的合并

    描述
    将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的
    输入:
    1->2->4,
    1->3->4
    输出:
    1->1->2->3->4->4

    思路:
    下图中红色箭头表明穿针的过程与方向

    1. 新建一个头节点,建立一个指针cur,作为针
    2. 把比较小的那个节点串起来,指针向前移,当前 cur指针也往前移
    3. 最后处理没有走完的链表,拼在最后
      在这里插入图片描述
    const mergeTwoLists = function(l1, l2) {
      #1 定义头结点,确保链表可以被访问到
      let head = new ListNode()
      // cur 这里就是咱们那根“针”
      let cur = head
      // “针”开始在 l1 和 l2 间穿梭了
      while(l1 && l2) {
          # 2.1如果 l1 的结点值较小,先串起 l1 的结点,l1向前走
          if(l1.val<=l2.val) { 
              cur.next = l1
              l1 = l1.next
          } else {
           #2.2 如果l2 较小时,串起 l2 结点,l2 向前一步
              cur.next = l2
              l2 = l2.next
          }
         #3 “针”在串起一个结点后,也会往前一步
          cur = cur.next 
      }
      #4 处理链表不等长的情况
      cur.next = l1!==null?l1:l2
      // 返回起始结点
      return head.next
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2 链表结点的删除

    1. 删除所有重复的元素

    描述
    给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

    输入:
    1->1->2
    输出: 1->2

    输入: 1->1->2->3->3
    输出: 1->2->3

    const deleteDuplicates = function(head) {
        #1 设定 cur 指针,初始位置为链表第一个结点
        let cur = head;
        #2 遍历链表
        while(cur != null && cur.next != null) {
            if(cur.val === cur.next.val) {
             # 2.1 若当前结点和它后面一个结点值相等(重复),删除靠后的那个结点(去重)
                cur.next = cur.next.next;
            } else {
             # 2.2 若不重复,继续遍历
                cur = cur.next;
            }
        }
        return head;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2 删除所有含有重复数字的结点

    描述
    给定一个排序链表,删除所有含有重复数字的结点,只保留原始链表中 没有重复出现的数字

    输入:
    1->2->3->3->4->4->5
    输出:
    1->2->5

    输入:
    1->1->1->2->3
    输出:
    2->3

    思路:
    链表的第一个结点,因为没有前驱结点,导致我们面对它无从下手。这时我们就可以用一个 dummy 结点来解决这个问题。
    1 定义dummy结点,指向头结点,curdummy遍历
    2. 遇到一样的,记录val的值,开始循环遍历,把一样的都删除。不一样则跳过
    3 返回dummy.next,即链表的起始节点
    在这里插入图片描述

    const deleteDuplicates = function(head) {
        #1 极端情况:0个或1个结点,则不会重复,直接返回
        if(!head || !head.next) {
            return head
        }
        #2 dummy 登场,dummy 永远指向头结点
        let dummy = new ListNode()  
        dummy.next = head   
        
        #3 cur 从 dummy 开始遍历
        let cur = dummy 
        // 当 cur 的后面有至少两个结点时
        while(cur.next && cur.next.next) {
            // 对 cur 后面的两个结点进行比较
            if(cur.next.val === cur.next.next.val) {
                // 若值重复,则记下这个值
                let val = cur.next.val
                // 反复地排查后面的元素是否存在多次重复该值的情况
                while(cur.next && cur.next.val===val) {
                    // 若有,则删除
                    cur.next = cur.next.next 
                }
            } else {
                // 若不重复,则正常遍历
                cur = cur.next
            }
        }
        // 返回链表的起始结点
        return dummy.next;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    dummy结点,模板代码

    const dummy = new ListNode()
    // 这里的 head 是链表原有的第一个结点
    dummy.next = head
    
    • 1
    • 2
    • 3

    3 删除链表的倒数第 N 个结点(快慢指针)

    描述
    给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

    给定一个链表: 1->2->3->4->5, 和 n = 2.
    当删除了倒数第二个结点后,链表变为 1->2->3->5.

    思路:

    1. 定义dummy结点作为头结点的前驱, 快慢指针都从dummy开始
    2. 快指针先走n步,
    3. 快慢指针开始同步走,
    4. 删除慢指针的后继,即倒数第n个结点
    5. 返回dummy.next,即头结点
      在这里插入图片描述
    const removeNthFromEnd = function(head, n) {
        #1 初始化 dummy 结点,指向头结点
        const dummy = new ListNode()
        dummy.next = head
        
        #2 初始化快慢指针,均指向dummy
        let fast = dummy
        let slow = dummy
    
        #3 快指针闷头走 n 步
        while(n!==0){
            fast = fast.next
            n--
        }
        
        #4 快慢指针一起走
        while(fast.next){
            fast = fast.next
            slow = slow.next
        }
        
        #5 慢指针删除自己的后继结点
        slow.next = slow.next.next
        // 返回头结点
        return dummy.next
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    4.0 链表的反转(多指针)

    描述
    定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点

    输入: 1->2->3->4->5->NULL
    输出: 5->4->3->2->1->NULL

    思路:

    1. 前驱结点pre,当前结点cur,后继结点next
    2. next指向pre,然后precur都继续后移
    3. 最后返回pre ,就是新链表的头结点
      在这里插入图片描述
    const reverseList = function(head) {
        #1 初始化前驱结点为 null
        let pre = null;
        #2 初始化目标结点为头结点
        let cur = head;
        
        #3 只要目标结点不为 null,遍历就得继续
        while (cur !== null) {
            // 记录一下 next 结点
            let next = cur.next;
            // 反转指针
            cur.next = pre;
            // pre 往前走一步
            pre = cur;
            // cur往前走一步
            cur = next;
        }
        // 反转结束后,pre 就会变成新链表的头结点
        return pre
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4.1 局部反转一个链表

    描述
    反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

    输入: 1->2->3->4->5->NULL, m = 2, n = 4
    输出: 1->4->3->2->5->NULL

    思路

    1. 先定义dummy,找到m的前驱,并保存下来 lefthead
    2. m作为pre,cur指向pre.next开始翻转
    3. leftHead指向prestart指向cur
      在这里插入图片描述
    // 入参是头结点、m、n
    const reverseBetween = function(head, m, n) {
        // 定义pre、cur,用leftHead来承接整个区间的前驱结点
        let pre,cur,leftHead
        #1  dummy结点
        const dummy = new ListNode()  
        dummy.next = head
        
        #2.0 p是一个游标,用于遍历,最初指向 dummy,往前走 m-1 步,走到整个区间的前驱结点处
        let p = dummy  
        for(let i=0;i<m-1;i++){
            p = p.next
        }
        #2.1 缓存这个前驱结点到 leftHead 里
        leftHead = p
        let start = leftHead.next   // start 是反转区间的第一个结点
        pre = start// pre 指向start
        cur = pre.next // cur 指向 start 的下一个结点
        
        #3 开始重复反转动作
        for(let i=m;i<n;i++){
            let next = cur.next// 记录一下 next 结点
            cur.next = pre // 反转指针
            pre = cur// pre 往前走一步
            cur = next // cur往前走一步
        }
        #4  leftHead 的后继结点此时为反转后的区间的第一个结点
        leftHead.next = pre
        start.next=cur // 将区间内反转后的最后一个结点 next 指向 cur
      
        return dummy.next  // dummy.next 永远指向链表头结点
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    5 判断链表是否成环

    1. 改变结点,flag
    const detectCycle = function(head) {
        while(head){
            if(head.flag){
                return head;
            }else{
                head.flag = true;
                head = head.next;
            }
        }
        return null;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2. 可以用map模拟 (个人喜欢,牢记)

    const detectCycle = function(head) {
    	const visited = new Map();
        while(head){
            if(visited.has(head)){
                return head;
            }else{
                visited.set(head)
                head = head.next;
            }
        }
        return null;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 快慢指针

    快指针走两步,慢指针走一步
    如果相等了就有环

    const detectCycle = function(head) {
       if(!head) return null;
       let slow=head,fast=head;
    	#1 快指针没到头就继续
       while (fast !== null) {
       		#2 慢指针走一步,快指针走两步
            slow = slow.next;
            if (fast.next !== null) {
                fast = fast.next.next; 
            } else {
                return null;
            }
    
    		#3 如果快慢指针相遇
            if (fast === slow) {
                let ptr = head;
                while (ptr !== slow) {
                    ptr = ptr.next;
                    slow = slow.next;
                }
                return ptr;
            }
        }
        return null;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    我们再额外使用一个指针 ptr
    起始,它指向链表头部
    ;随后,它和slow 每次向后移动一个位置。最终,它们会在入环点相遇。

  • 相关阅读:
    【转】浅谈威胁狩猎(Threat Hunting)
    Oracle课程-深入学习文档
    分页查询实体和分页响应实体的使用
    设计模式简介
    SpringCloud环境搭建及入门案例
    3、深入理解synchronized
    LeetCode:4. 寻找两个正序数组的中位数
    @Async注解的使用方法
    windows查找指定端口程序,终止程序
    【考研数学】线性代数第六章 —— 二次型(2,基本定理及二次型标准化方法)
  • 原文地址:https://blog.csdn.net/weixin_40852935/article/details/125406130