[前端]_为什么操作链表看起来像操作普通类型

754 阅读4分钟

今天好兄弟遇到问题了。为什么链表的操作看起来像是基础类型,又像是引用类型。刚学的时候我也有过这样子的迷惑,分享一下我的经验。

image.png

首先回答第一个问题,链表在前端中本质上就是对象。但是这是一个嵌套对象,因为它包裹着下一个节点,下一个节点又包裹着下下个节点。那么为什么能直接进行赋值呢? 直接赋值是用来缓存当前节点的位置。比如: 在链表 1 => 2 => 3 中,你每次要给节点的位置加1, 那么你会通过一个while循环来实现。cur = cur.next可以不断取到下一个节点,但是当你要返回原本的头节点的时候,你就需要在循环开始前, 前用变量head = cur 来缓存原本头节点的位置。这里的知识点分为以下几个小点:

  1. 首先明确一点 head = head.next,会不会对当前节点有影响?答案是肯定的,这里改变了head在链表中的位置。
  2. 但是对整条链表有没有影响?你会发现并没有,因为每个节点对象的属性并没有发生变化。
  3. 所以如果加个变量去缓存链表的头节点,这时候返回你会发现链表还是原来的链表。
  4. 然后再搞懂head.next = head, 会不会对当前节点有影响?答案同样是肯定的,因为已经改变了对象中的next属性。
  5. 对整条链表有没有影响呢? 答案也是肯定的,因为链表中的某个对象发生了实质性的改变。
  6. 然后回到翻转链表这道题目中来,首先我们需要不断进行head = head.next,去改变每一个位置节点的next的指向。
  7. 那我们改变当前节点的时候,为什么不能直接把当前节点的next指向prev上一个节点呢? 因为你直接指向了prev上一个节点后,你下一轮怎么办,你就拿不到下一个节点了, 所以我们修改当前节点的next指向之前,需要缓存一下原本的next节点。
  8. 这样子当前这一轮指向修改完成后,下一轮才能继续接着进行。

举例

举个栗子。

我们要修改链表 1 => 2 => 3

一开始创建一个新链表newHead = null, 然后进入第一轮的时候,我们拿到的是1这个节点, 我们在修改节点1的next指向之前,需要先缓存原本的next节点2,因为下一轮循环要修改节点2。 然后呢, 我们把节点1的next, 指向到 newHead身上, 这样子就形成了 1 => null, 然后我们把1 => null赋值,成位新的newHead, 同时当前节点向后走一步, 对节点2做同样的修改。

实战

剑指 Offer II 024. 反转链表

给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。

 

示例 1:

输入: head = [1,2,3,4,5]
输出: [5,4,3,2,1]

示例 2:

输入: head = [1,2]
输出: [2,1]

示例 3:

输入: head = []
输出: []

 

提示:

  • 链表中节点的数目范围是 [0, 5000]
  • -5000 <= Node.val <= 5000

代码

/**
 * 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) {
    let newHead = null; // 创建新链表,这一步没错
    
    // 这一步是为了缓存原本的头节点, 比如删除链表中的某个节点的时候,你删除完,你需要返回原本的头节点
    // 但是这道题其实我们可以直接操作head,因为待会儿我们只需要返回newHead
    let nowHead = head;

    // 我们需要不断遍历,也就是不断进行nowHead = nowHead.next
    // 但是我们又要更改当前节点next的指向,所以我们在更改前,我们缓存原本的下一个节点
    while (nowHead) {
        const next = nowHead.next; // 缓存原本的下一个节点,确保我们下一轮改它
        nowHead.next = newHead; // 改变当前节点的next指针,指向自己的上一个节点
        newHead = nowHead; // 上一个节点添加了当前节点作为链表头
        nowHead = next; // 下一轮改原本的下一个节点, 不要影响我们的原计划
    }

    return newHead;
};

花了大半个小时去组织语言想着怎么解释能让大家都尽量看得懂。希望能够帮到你呀好兄弟。我尽力了哈哈!