处理链表的本质,是处理链表结点之间的指针关系

简介: 处理链表的本质,是处理链表结点之间的指针关系

处理链表的本质,是处理链表结点之间的指针关系


最近在看数据结构和算法,努力总结出道~

TL;DR

  • JS里,链表就是嵌套的对象,{val:1,next:{val:2,next:...}}
  • 链表里的指针,听起来很抽象,其实就是部分链表,依旧是嵌套对象,p是{val:1,next:{val:2,next:...}}
  • 指针往后挪动,其实就是p = p.next,也就是p变成{val:2,next:...}
  • 链表的最后一个节点,next肯定是空的
  • 处理链表的时候,一般为了边界问题,很多时候先创建一个空节点,指向链表,有备无患吧

基础:JS 怎么表示链表

JS 里,是没有链表这种数据类型的,所以用对象去表达链表

// 链表的节点类
function ListNode(val) {
  this.val = val;
  this.next = null;
}

如果一个链表是1->2->4,JS 的表达方式

{
  val:1,
  next:{
    val:2,
    next:{
      val:4,
      next:null
    }
  }
}

网络异常,图片无法展示
|

基础:遍历一个链表

遍历链表的是时候,往往借助指针,指针往后挪,挪到空的时候就遍历完了

function walkList(list) {
  // 先指向第一个节点
  let p = list;
  while (p) {
    // 打印当前指针对应结点的值
    console.log(p.val);
    // 指针往后移动
    p = p.next;
  }
  // p为空的时候,就是遍历完了
  console.log("遍历完了");
}

增加一个节点

1->2->4来说,如果想增加一个节点,变成1->2->3->4新增的节点 3 的 next 指针指向 4,2 的 next 指针指向 3

// 先创建新节点
const newNode = new ListNode(3);
// 找到新节点前一个节点的指针,也就是这个指针指向2节点
const p2 = list1.next;
// 找到新节点后一个节点的指针,也就是这个指针指向4节点
const p4 = p2.next;
// 新节点的next指向 后一个节点的指针
newNode.next = p4;
// 新节点的前一个指针,指向新节点
p2.next = newNode;

删除一个节点

1->2->4来说,如果想删除一个节点,变成1->41 的节点直接指向 4 就好了

// 这里注意,防止删除的时候最后一个节点
p1.next = p1.next.next ? p1.next.next :null;

基础:新建一个链表

新建一个链表1->2,作为边界处理,一般会有一个空值的头结点

// 新节点
const n1 = new ListNode(1)
const n2 = new ListNode(2)
// 作为边界处理,会习惯性的将第一个节点的值置为空
// 为了后期知道首节点的引用,所以必须有个head指针
let head = new ListNode();
// 新链表的指针,这里p是指针,会移动
let p = head
p.next = n1
// 指针移动
p = p.next
// 链表加长
p.next = n2
// 指针移动
p = p.next
const newList = head.next

练习:合并链表

网络异常,图片无法展示
|

  • 新建链表
  • 链表 1 和链表 2 各一个指针,遍历
  • 比大小,然后放到新链表里

可以想象成每个节点都是扣子,新链表的指针像线一样,将扣子穿起来。

function ListNode(val, next) {
  this.val = val;
  this.next = null;
}
function mergeTwoLists(l1, l2) {
  let p1 = l1;
  let p2 = l2;
  // 首个空节点用来规避边界问题,引用必须保存
  let head = new ListNode();
  head.next = list;
  // 新链表的指针
  let p = head;
  while (p1 && p2) {
    if (p1.val <= p2.val) {
      // 链表加长
      p.next = new ListNode(p1.val);
      // 指针移动
      p1 = p1.next;
      p = p.next;
    } else {
      // 链表加长
      p.next = new ListNode(p2.val);
      // 指针移动
      p2 = p2.next;
      p = p.next;
    }
  }
  // 这里链表1、2肯定有一个遍历完了,剩下的接上去就行
  p.next = p1 ? p1 : p2;
  // 头结点的引用就是在这时候用到的,当然首个空节点不要
  return head.next;
}

可以看下官方视频

练习:删除重复节点

网络异常,图片无法展示
|

  • 因为有序,所以重复的节点肯定是相邻的
  • 考虑边界问题,额外增加一个头结点
  • 从头遍历,指针指向当前节点,判断和下一个节点是相同的话,删除下一个节点
  • 不相同的话,指针向前挪动
function ListNode(val, next) {
  this.val = val;
  this.next = null;
}
var deleteDuplicates = function (head) {
  let head = new ListNode();
  head.next = list;
  let p = head;
  while (p && p.next) {
    // 相邻节点是不是相等,这里注意相等的话,一直删除节点,直到不相等的时候,指针才挪动
    while (p && p.next && p.val === p.next.val) {
      // 删除的时候,有下下节点,直接修改指向;没有下下节点的话,就是尾结点,直接置为空
      p.next = p.next.next ? p.next.next : null;
    }
    // 指针挪动
    p = p.next;
  }
  return head.next;
};

虽然看着两层循环,但其实,每个节点只遍历一次,所以时间复杂度是 O(n),空间复杂度是 O(1).

官方视频解释

练习:删除重复节点升级

网络异常,图片无法展示
|

这里,重复的节点本身也会删除。

本身也被删除,非常重要!

因为链表的删除,必须要知道前置节点! 所以,我们遍历的时候,指针指向当前节点,但比较的是下一个节点和下下一个节点

  • 边界的问题,加上空的前置节点
  • 当值相同的时候,将这两个节点删除,然后看下一个节点是不是和刚刚的值相等,相等的话继续删除,一直到不相等,这样就会将删除的相邻节点都会删除
  • 当值不同的时候,指针移动
function ListNode(val, next) {
  this.val = val;
  this.next = null;
}
var deleteDuplicates = function (list) {
  let head = new ListNode();
  head.next = list;
  let p = head;
  // 因为比较指针后两个节点,所以必须判断在的情况
  while (p && p.next && p.next.next) {
    /* 节点值相同的全部删除 开始 */
    // 开始是两个节点值相同,将这两个节点都删除
    if (p.next.val === p.next.next.val) {
      let cur = p.next.val;
      p.next = p.next.next.next ? p.next.next.next : null;
      // 然后看下一个节点是不是和删除的节点一样,一样的话,接着删除,直到下一个节点的值和删除的节点不一样
      while (p.next && p.next.val === cur) {
        p.next = p.next.next ? p.next.next : null;
      }
    /* 节点值相同的全部删除 结束 */
    } else {
      p = p.next;
    }
  }
  return head.next;
};

时间复杂度依旧是O(n),时间复杂度是O(1)

官方解析

引用

目录
相关文章
|
1月前
|
算法
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点
65 1
|
19天前
|
存储 算法 搜索推荐
链表的中间结点
【10月更文挑战第24天】链表的中间结点是链表操作中的一个重要概念,通过快慢指针法等方法可以高效地找到它。中间结点在数据分割、平衡检测、算法应用等方面都有着重要的意义。在实际编程中,理解和掌握寻找中间结点的方法对于解决链表相关问题具有重要价值。
10 1
|
1月前
链表指针的传参,传值和传地址
本文讨论了链表操作中指针传参的问题,特别是指针的传值与传地址的区别,并提供了修正代码,以确保链表插入操作能正确地修改指针指向的地址。
16 1
链表指针的传参,传值和传地址
|
2月前
链表的中间结点
链表的中间结点
178 57
|
1月前
|
存储
一篇文章了解区分指针数组,数组指针,函数指针,链表。
一篇文章了解区分指针数组,数组指针,函数指针,链表。
18 0
|
1月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
22 0
|
1月前
【LeetCode 09】19 删除链表的倒数第 N 个结点
【LeetCode 09】19 删除链表的倒数第 N 个结点
16 0
|
3月前
|
算法
LeetCode第19题删除链表的倒数第 N 个结点
该文章介绍了 LeetCode 第 19 题删除链表的倒数第 N 个结点的解法,通过使用快慢双指针,先将快指针移动 n 步,然后快慢指针一起遍历,直到快指针到达链尾,从而找到倒数第 N 个结点的前一个结点进行删除,同时总结了快慢指针可减少链表遍历次数的特点。
LeetCode第19题删除链表的倒数第 N 个结点
|
4月前
【数据结构OJ题】复制带随机指针的链表
力扣题目——复制带随机指针的链表
51 1
【数据结构OJ题】复制带随机指针的链表
|
3月前
|
Python
【Leetcode刷题Python】138. 复制带随机指针的链表
LeetCode上题目“138. 复制带随机指针的链表”的Python解决方案,包括两种方法:一种是在每个节点后复制一个新节点然后再分离出来形成新链表;另一种是构建一个字典来跟踪原始节点与其副本之间的映射关系,从而处理新链表的构建。
20 1