【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
};

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;
};

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;
};

dummy结点,模板代码

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

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
};

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
};

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 永远指向链表头结点
};

5 判断链表是否成环

  1. 改变结点,flag
const detectCycle = function(head) {
    while(head){
        if(head.flag){
            return head;
        }else{
            head.flag = true;
            head = head.next;
        }
    }
    return null;
};

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. 快慢指针

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

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;
};

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值