JavaScript:链表

news2024/9/24 15:21:16

文章目录

  • 链表
    • js没有链表吗
    • 203. 移除链表元素
      • 为什么设置虚拟头节点
      • 思路与代码分析
      • 上面代码补充分析
      • 注意:为什么把虚拟头节点赋值给 cur
    • 204. 设计链表
    • 206. 反转链表
      • 双指针法--具体思路见代码
      • 双指针法具体分析(上面代码看懂这里可以忽略)
      • 递归法思路略
      • 补充:head->next = p 和 p=head->next(可以跳过)
      • 注意:a.next = b
    • 142.环形链表II
      • 使用快慢指针的方法
      • 判断链表是否有环
      • 如果有环,如何找到这个环的入口
      • 代码及思路分析
      • 想不通就画图模拟一下过程或者看上面提供的动态图
    • 总结
      • 什么时候使用虚拟头节点?
      • 为什么要使用虚拟头节点?
      • 双指针的使用
      • 设置虚拟头节点为什么还要赋值给另外的变量?

链表

这里链表的使用部分题目也会用到双指针法

js没有链表吗

JavaScript 是一门动态语言,它没有提供原生的链表数据结构。但是可以使用对象和数组来模拟链表
例如,可以使用对象来表示链表的节点,每个节点包含一个值和指向下一个节点的引用。定义一个链表类如下:

class LinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
  }

  // 在链表末尾添加节点append(value) {
    const node = { value, next: null };
    if (this.tail) {
      this.tail.next = node;
    }
    this.tail = node;
    if (!this.head) {
      this.head = node;
    }
  }

  // 在链表中查找节点find(value) {
    let node = this.head;
    while (node) {
      if (node.value === value) {
        return node;
      }
      node = node.next;
    }
    return null;
  }

  // 在链表中删除节点delete(value) {
    if (!this.head) {
      return;
    }
    // 如果是头节点while (this.head && this.head.value === value) {
      this.head = this.head.next;
    }
    let node = this.head;
    while (node.next) {
      if (node.next.value === value) {
        node.next = node.next.next;
      } else {
        node = node.next;
      }
    }
    if (this.tail.value === value) {
      this.tail = node;
    }
  }
}

以上代码实现了链表的基本操作:添加节点、查找节点、删除节点等。对于更复杂的链表操作,可以根据需要进行扩展。
当然,如果需要在 JavaScript 中使用链表,也可以使用第三方库实现,如immutable.js、list.js、lazy.js等。这些库中都提供了链表等多种数据结构的实现。

203. 移除链表元素

为什么设置虚拟头节点

链表的第一个结点有可能需要添加或删除的时候,如果没有虚拟头结点
就要分两种情况实现添加或删除节点的操作
一种是头结点的删除,一种是其他结点的删除
有虚拟头结点,那么实际的头结点变相成为了其他结点
操作就是一样的

思路与代码分析

删除val节点:只需要将当前节点的上一个节点指向下一个节点即可

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var removeElements = function(head, val) {
    // 添加一个虚拟头节点 head 0
    const ret = new ListNode(0, head)
    // 新建一个变量去改变ret内容,我也不是很清楚为什么临时变量可以改变初始内容(见下面注意解析)
    let cur = ret
    while(cur.next) {
        // 如果下一个地址的data===val,那么就将下一个地址赋值为下下个地址
        if(cur.next.val === val) {
            cur.next = cur.next.next
            continue
        }
        // 如果不等于,则后移下一位地址进行片段
        else {
            cur = cur.next
        }
        
    }
    return ret.next

};

上面代码补充分析

// 如果是首,不好操作,所以我们可以设一个虚拟头节点
    // ret 是头节点,头节点知道了整个链表就知道了
    const ret = new ListNode(0, head)
    // 这里 赋值给 cur  表示当前节点()
    let cur = ret
    while(cur.next) {
        if (cur.next.val === val) {
            cur.next = cur.next.next
            continue
        } else {
            cur = cur.next
        }
    }
    // 不能直接返回ret 因为设置了虚拟头节点 所以我们需要从下一个返回
    return ret.next

注意:为什么把虚拟头节点赋值给 cur

上面代码把 ret 赋值给cur目的就是为了,操作链表的时候不影响到头节点
我们操作链表就用 cur去操作,最后操作后的链表就用 ret返回改变后的链表

204. 设计链表

class LinkNode {
   constructor(val, next) {
       this.val = val;
       this.next = next;
   }
}

/**
* Initialize your data structure here.
* 单链表 储存头尾节点 和 节点数量
*/
var MyLinkedList = function() {
   this._size = 0;
   this._tail = null;
   this._head = null;
};

/**
* Get the value of the index-th node in the linked list. If the index is invalid, return -1. 
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.getNode = function(index) {
   if(index < 0 || index >= this._size) return null;
   // 创建虚拟头节点
   let cur = new LinkNode(0, this._head);
   // 0 -> head
   while(index-- >= 0) {
       cur = cur.next;
   }
   return cur;
};
MyLinkedList.prototype.get = function(index) {
   if(index < 0 || index >= this._size) return -1;
   // 获取当前节点
   return this.getNode(index).val;
};

/**
* Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. 
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function(val) {
   const node = new LinkNode(val, this._head);
   this._head = node;
   this._size++;
   if(!this._tail) {
       this._tail = node;
   }
};

/**
* Append a node of value val to the last element of the linked list. 
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function(val) {
   const node = new LinkNode(val, null);
   this._size++;
   if(this._tail) {
       this._tail.next = node;
       this._tail = node;
       return;
   }
   this._tail = node;
   this._head = node;
};

/**
* Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. 
* @param {number} index 
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function(index, val) {
   if(index > this._size) return;
   if(index <= 0) {
       this.addAtHead(val);
       return;
   }
   if(index === this._size) {
       this.addAtTail(val);
       return;
   }
   // 获取目标节点的上一个的节点
   const node = this.getNode(index - 1);
   node.next = new LinkNode(val, node.next);
   this._size++;
};

/**
* Delete the index-th node in the linked list, if the index is valid. 
* @param {number} index
* @return {void}
*/
MyLinkedList.prototype.deleteAtIndex = function(index) {
   if(index < 0 || index >= this._size) return;
   if(index === 0) {
       this._head = this._head.next;
       // 如果删除的这个节点同时是尾节点,要处理尾节点
       if(index === this._size - 1){
           this._tail = this._head
       }
       this._size--;
       return;
   }
   // 获取目标节点的上一个的节点
   const node = this.getNode(index - 1);    
   node.next = node.next.next;
   // 处理尾节点
   if(index === this._size - 1) {
       this._tail = node;
   }
   this._size--;
};

// MyLinkedList.prototype.out = function() {
//     let cur = this._head;
//     const res = [];
//     while(cur) {
//         res.push(cur.val);
//         cur = cur.next;
//     }
// };
/**
* Your MyLinkedList object will be instantiated and called as such:
* var obj = new MyLinkedList()
* var param_1 = obj.get(index)
* obj.addAtHead(val)
* obj.addAtTail(val)
* obj.addAtIndex(index,val)
* obj.deleteAtIndex(index)
*/

206. 反转链表

双指针法–具体思路见代码

如果再定义一个新的链表,实现链表元素的反转,其实这是对内存空间的浪费。
其实只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    // 如果头节点不存在 或 头节点下一个节点不存在
    if(!head || !head.next) return head

    // 虚拟头节点
    const ret = new ListNode(0, head)

    // 利用双指针:前一个指针pre 当前指针cur 
    let pre = null
    let cur = head

    // 反转:即让当前指针指向前一个指针

    while(cur) {
        // 保存一份当前节点指向的下一个节点
        const _ = cur.next
        // 反转:即让当前指针指向前一个指针
        cur.next = pre
        // 向后移动前一个节点
        pre = cur
        // 向后移动当前节点
        cur = _
        
    }

    // cur 为空时,上面循环结束,次吃末尾节点就是pre 也就是反转链表的头节点
    return pre
};

双指针法具体分析(上面代码看懂这里可以忽略)

  1. 首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
  2. 然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
  3. 为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
  4. 接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。
  5. 最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点
    在这里插入图片描述

补充代码分析:

while(cur) {
        // 这里需要把cur.next 存起来,因为后面cur.next 会指向前面的 pre
        // 此时cur.next就变化了,所以我们需要把原始值保存一份
        temp = cur.next
        cur.next = pre
        // 先让pre移到当前cur
        pre = cur
        // 在赋值cur(如果先赋值了cur,pre移到谁呢?所以pre先移动)
        cur = temp
    }

递归法思路略

// 递归:
var reverse = function(pre, head) {
    if(!head) return pre;
    const temp = head.next;
    head.next = pre;
    pre = head
    return reverse(pre, temp);
}
var reverseList = function(head) {
    return reverse(null, head);
};

// 递归2
var reverse = function(head) {
    if(!head || !head.next) return head;// 从后往前翻
    const pre = reverse(head.next);
    head.next = pre.next;
    pre.next = head;return head;
}
var reverseList = function(head) {
    let cur = head;
    while(cur && cur.next) {
        cur = cur.next;
    }
    reverse(head);return cur;
};

补充:head->next = p 和 p=head->next(可以跳过)

谁A等于谁B,就是谁A指向谁B
原文链接:https://blog.csdn.net/skychaofan/article/details/46815007

head->next = p;和p=head->next;是不同的,当p = head->next;时,我们可以认为是把p指针指向了head->next,即是把head->next 的值赋给p,而当head->next = p时,就是head->next 的指针指向了p指针,即是把p的值赋给head->next。这一点是一开始接触链表的人最容易犯的错误。
在这里插入图片描述
在这里插入图片描述

由上图我们分析可以知道,当p = head->next 时,p是指向head->next的,可以说与head的链表是没有“干扰作用”的;而当head->next = p时就不同了,这就改变了原来链表指向的方向了。所以,一般来说,p = head->next 用于显示链表,而head->next = p;多用于插入节点。

如上函数,输出的p->name和head->next->name应该是相等的,两者之间是赋值的关系。

注意:a.next = b

**上面的分析很罗嗦
就目前链表做题:只需要知道 a.next = b (a的下一个节点是b)
**

JavaScript 里面的.next 就是以前的c里面的->next

具体可以学习以下博客讲解:(写的很好)
算法:JS 中的链表简介_js链表_仙女爱吃鱼的博客-CSDN博客

142.环形链表II

使用快慢指针的方法

判断链表是否有环

可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
在这里插入图片描述

如果有环,如何找到这个环的入口

推导原理略

从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点。

在这里插入图片描述

代码及思路分析

先2,1走到相遇节点,之后一个从相遇节点开始走1,一个从头节点走1,之后再次相遇的节点就是环形入口

/*
 * @lc app=leetcode.cn id=142 lang=javascript
 *
 * [142] 环形链表 II
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    /* 
        快慢指针:快指针先走两步,慢指针走一步,相遇了说明有环
    */
    if(!head || !head.next) return null
    let slow = head.next, fast = head.next.next
    //  2 1 走,还没遇到
    while (fast && fast.next && fast!=slow) {
        slow = slow.next
        fast = fast.next.next
    }
    // 如果走到null 则返回null
    if(!fast || !fast.next) return null
    // 没有走到空,且到达相遇的时候,slow回到头节点,fast不变,再 1 1 走 相遇的地方便是环入口的地方
    slow = head
    // 1 1 走没有相遇的时候,就一直走
    while(fast != slow) {
        fast = fast.next
        slow =slow.next
    }
    // 1 1 相遇了
    return slow
};
// @lc code=end

想不通就画图模拟一下过程或者看上面提供的动态图

在这里插入图片描述

总结

什么时候使用虚拟头节点?

链表的第一个结点有可能需要添加或删除的时候,如果没有虚拟头结点
就要分两种情况实现添加或删除节点的操作
一种是头结点的删除,一种是其他结点的删除
有虚拟头结点,那么实际的头结点变相成为了其他结点
操作就是一样的

为什么要使用虚拟头节点?

可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行操作了。

双指针的使用

可以看数组部分双指针的使用方法,方法都是类似的,需要灵活使用

设置虚拟头节点为什么还要赋值给另外的变量?

例如把 ret 赋值给cur目的就是为了操作链表的时候不影响到头节点

我们操作链表就用 cur去操作,最后操作后的链表就用 ret返回改变后的链表

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/488310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

注册openai用什么邮箱-中文版ChatGPT有哪些

注册openai用什么邮箱 你可以使用任何有效的电子邮箱地址注册 OpenAI&#xff0c;例如 Gmail、Outlook、Yahoo 等。请注意&#xff0c;您需要通过验证您的电子邮件地址才能完成注册过程。 中文版ChatGPT哪个好 中文版的 ChatGPT 在自然语言处理、语言生成等领域拥有广泛的应用…

4.4.1内核编译

内核源码下载地址&#xff1a; https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.4.1.tar.gz 安装依赖包&#xff1a;报错就装 cp /boot/config-xxx ./.config make mrproper make menuconfig,然后save保存&#xff0c;退出 make -j4 //四线程编译 sudo ma…

电气电工相关专业知识及名词解释

一、电流电压 火线、零线、地线&#xff1a;火线和零线的区别就是&#xff1a;火线带电&#xff0c;零线不带电。火线是传电流的&#xff0c;而零线是回流的。 红色是火线&#xff0c;零线一般是绿色的&#xff0c;通常可用电笔来测。电笔一头亮了是火线&#xff0c;不亮的则…

网络计算模式复习(一)

二层C/S架构 概念&#xff1a;C/S架构是一种典型的两层架构&#xff0c;其全称是Client/Server&#xff0c;即客户端服务器端架构。 其中客户端包含一个或多个在用户的电脑上运行的程序。服务器端有两种&#xff0c;一种是数据库服务器端&#xff0c;客户通过数据库连接访问服…

GBASE荣获2023数字中国创新大赛·信创赛道华北赛区一等奖

日前&#xff0c;2023数字中国创新大赛信创赛道华北赛区区域赛圆满结束。经过激烈角逐&#xff0c;GBASE参赛作品「多模多态企业级分布式数据库」GBase 8c荣获华北赛区一等奖&#xff0c;将被选送到全国总决赛&#xff0c;参与最后的冠军角逐。 数字中国创新大赛信创赛道是我国…

RIS/PACS系统源码,工作站无缝集成三维重建模块,实现极速的三维后处理

RIS/PACS系统源码 带三维重建和还原的PACS源码 RIS/PACS系统源码在预约登记、分诊叫号、技师检查、诊断报告、临床浏览、科室管理等环节满足全院相关科室的要求。在医学影像下载、浏览、处理中满足速度快、强化常用功能、方便阅片等要求。满足放射、超声、内镜、病理等影像科…

REMIX:重构·连接·进化|徐亚波博士D3大会演讲实录

“欢迎大家和数说故事一起来到新世界&#xff0c;和我们一起&#xff0c;来玩一个AI普适场景的无限游戏。” 在数说故事第六届D3智能营销峰会上&#xff0c;数说故事创始人兼CEO徐亚波博士带来「REMIX——重构连接进化」的主题分享&#xff0c;聚焦“ChatGPT开启的AGI时代有什么…

分块思想(Sqrt Decomposition)的实现(golang)

前言 Sqrt Decomposition 是一种数据结构&#xff0c;能够在O(1)时间复杂度内完成数组元素值的查询和更新&#xff0c;在 O ( n ) O(\sqrt{n}) O(n ​) 时间复杂度内完成数组某个区间属性值的查询和批量更新某个区间的值。这里的属性 可以是区间的和、最小值、最大值等。 说到…

DDR3(MIG核配置官方demoFPGA代码实现及仿真)

由于直接对 DDR3 进行控制很复杂&#xff0c;因此一般使用 MIG IP 来实现&#xff0c;同时为了更简单地使用 MIG IP&#xff0c;我们采用 AXI4 总线协议进行控制。下面首先介绍 MIG IP 的配置&#xff0c;然后看看官方 demo &#xff08;里面包含一个仿真要用到的 DDR3 模型&am…

Android Jetpack:利用Palette进行图片取色

与产品MM那些事 新来一个产品MM&#xff0c;因为比较平&#xff0c;我们就叫她A妹吧。A妹来第一天就指出&#xff1a;页面顶部的Banner广告位的背景是白色的&#xff0c;太单调啦&#xff0c;人家不喜欢啦&#xff0c;需要根据广告图片的内容自动切换背景颜色&#xff0c;颜色…

送了老弟一台 Linux 服务器,它又懵了!

大家好&#xff0c;我是鱼皮。 前两天我学编程的老弟小阿巴过生日&#xff0c;我问他想要什么礼物。 本来以为他会要什么游戏机、Q 币卡、鼠标键盘啥的&#xff0c;结果小阿巴说&#xff1a;我想要一台服务器。 鱼皮听了&#xff0c;不禁称赞道&#xff1a;真是个学编程的好苗…

演讲回顾 | 释放Atlassian工具的力量

2023年4月14日&#xff0c;Atlassian中国合作伙伴企业日上海站圆满落幕。作为Atlassian全球白金合作伙伴、云专业伙伴&#xff0c;龙智携客户小米参与了此次活动。 小米集团信息技术部SRE薛世英为大家带来主题为《小米集团Jira实战&#xff1a;如何在高负载状态下保持Jira性能与…

Html技术

软件架构&#xff1a; 架构访问端服务端C/SClientServerB/SBrowserServer Html&#xff1a; Hyper Text Markup Language &#xff08;超文本标记语言&#xff09; 简写&#xff1a;HTML HTML 通过标签来标记要显示的网页中的各个部分。 网页文件本身是一种文本文件&#xff0…

Redis类型操作

目录 一、List&#xff08;列表&#xff09; 1.List模板 2.List基本操作(不区分大小写) 2.1添加命令 2.2移除命令 2.3下标索引 2.4列表长度 2.5移除指定值 2.6List截断 2.7移除列表最后一个元素并将他移到新的列表中 2.8替换操作 2.9插入命令 二、Set(集合) 1.Set定义 2…

日撸 Java 三百行day41

文章目录 说明day41 顺序查找与折半查找1.顺序查找2.折半查找3.代码 说明 闵老师的文章链接&#xff1a; 日撸 Java 三百行&#xff08;总述&#xff09;_minfanphd的博客-CSDN博客 自己也把手敲的代码放在了github上维护&#xff1a;https://github.com/fulisha-ok/sampledat…

微信小程序蓝牙功能开发与问题记录

一、蓝牙支持情况 1. 微信小程序对蓝牙的支持情况 目前普遍使用的蓝牙规格&#xff1a;经典蓝牙和蓝牙低功耗。 经典蓝牙&#xff08;蓝牙基础率/增强数据率&#xff09;&#xff1a;常用在对数据传输带宽有一定要求的大数据量传输场景上&#xff0c;比如需要传输音频数据的…

AI工具和用法汇总—集合的集合

AI 工具和用法汇总 汇集整理 by Staok/瞰百&#xff0c;源于相关资料在我这慢慢越积累越多&#xff0c;到了不得不梳理的程度。 文中有许多内容作者还没有亲自尝试&#xff0c;所以很多内容只是罗列&#xff0c;但信息大源都已给出&#xff0c;授人以渔&#xff0c;欢迎 PR 补…

hadoop3.2.1+hive3.1.2-docker安装

Hadoop 1.拉取镜像 docker pull hadoop_hive:32.运行容器 建立hadoop用的内部网络(此步出错&#xff0c;若与其它网段冲突&#xff0c;可省略) #指定固定ip号段 docker network create --driverbridge --subnet172.17.0.1/16 hadoop建立Master容器&#xff0c;映射端口 10…

滚动加载数据

效果图: 综合使用后,觉得还是以下绑定 div监听滚动条的方式好用,有的可以监听滚轮滚动,但是监听不到鼠标拖动滚动条,下面这种方式两者都可以监测到↓ <template><div class"scrollTest-container" id"scrollTestContainer"><div class&quo…

简单分享微信小程序上的招聘链接怎么做

招聘小程序的主要用户就是企业招聘端和找工作人员的用户端,下面从这两个端来对招聘小程序开发的功能进行介绍。 企业端功能 1、岗位发布:企业根据自身岗位需求,在招聘app上发布招聘岗位及所需技能。 2.简历筛选:根据求职者提交的简历选择合适的简历,并对公开发布的简历进行筛…