【LeetCode刷题日记】:设计链表全解析

🔥个人主页:北极的代码(欢迎来访)

🎬作者简介:java后端学习者

❄️个人专栏:苍穹外卖日记SSM框架深入JavaWeb

命运的结局尽可永在,不屈的挑战却不可须臾或缺!

前言:前面由于忙着整理苍穹外卖,耽搁了算法的学习,进度有点慢了,下一阶段主要学习算法,再整顿一下,准备开黑马点评了,暂时先用算法过渡一下吧。

设计链表

题目背景:LeetCode707

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

题目答案:

java 复制代码
//单链表
class MyLinkedList {

    class ListNode {
        int val;
        ListNode next;
        ListNode(int val) {
            this.val=val;
        }
    }
    //size存储链表元素的个数
    private int size;
    //注意这里记录的是虚拟头结点
    private ListNode head;

    //初始化链表
    public MyLinkedList() {
        this.size = 0;
        this.head = new ListNode(0);
    }

    //获取第index个节点的数值,注意index是从0开始的,第0个节点就是虚拟头结点
    public int get(int index) {
        //如果index非法,返回-1
        if (index < 0 || index >= size) {
            return -1;
        }
        ListNode cur = head;
        //第0个节点是虚拟头节点,所以查找第 index+1 个节点
        for (int i = 0; i <= index; i++) {
            cur = cur.next;
        }
        return cur.val;
    }

    public void addAtHead(int val) {
        ListNode newNode = new ListNode(val);
        newNode.next = head.next;
        head.next = newNode;
        size++;

        // 在链表最前面插入一个节点,等价于在第0个元素前添加
        // addAtIndex(0, val);
    }

    
    public void addAtTail(int val) {
        ListNode newNode = new ListNode(val);
        ListNode cur = head;
        while (cur.next != null) {
            cur = cur.next;
        }
        cur.next = newNode;
        size++;

        // 在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
        // addAtIndex(size, val);
    }

    // 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果 index 大于链表的长度,则返回空
    public void addAtIndex(int index, int val) {
        if (index < 0 || index > size) {
            return;
        }

        //找到要插入节点的前驱
        ListNode pre = head;
        for (int i = 0; i < index; i++) {
            pre = pre.next;
        }
        ListNode newNode = new ListNode(val);
        newNode.next = pre.next;
        pre.next = newNode;
        size++;
    }

    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        
        //因为有虚拟头节点,所以不用对index=0的情况进行特殊处理
        ListNode pre = head;
        for (int i = 0; i < index ; i++) {
            pre = pre.next;
        }
        pre.next = pre.next.next;
        size--;
    }
}

题目分析:

这是一个链表的基础操作题,类似于在数组中的增删操作,但是链表肯定是比数组要复杂一些的,但是搞懂原理实际上也没什么太大的区别。

我们在操作这些方法时,通常要设置一个虚拟头节点,不了解的我在前面详细讲解过了,主要就是为了简化操作。

获取链表第index个节点的数值

由于链表没有数组的索引,获取链表值的时候我们不能直接查询,而是要从头开始遍历,直到查询到我们需要的节点。

在这里我们定义了虚拟头节点,那这样来说的话**,我们是从0节点开始遍历也就是我们的虚拟头节点,但是我们需要理解的是,如果题目里要查询的index=0,这里的节点是链表的真正头节点,而到我们这里用虚拟头结点的方法的话,0节点就是虚拟头节点,因此要查询题目中给的,就是虚拟头节点之后的一个节点。**

最直观的例子

复制代码
head(虚拟头) → 节点10 → 节点20 → 节点30
用户认为的index:      0      1      2

因此循环条件就需要index+1

虚拟头(内部0) → 真实节点A(题目0) → 真实节点B(题目1) → 真实节点C(题目2)

  • 想要题目 index = 0 → 走 1 步
  • 想要题目 index = 1 → 走 2 步
  • 想要题目 index = 2 → 走 3 步

所以:题目要 index,我们就从虚拟头往后走 index+1 步

然后从虚拟头节点开始遍历**,最后到index+1返回我们需要查询的。**


void addAtHead(int val)

将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。

题目解析:

因为我们设置了虚拟头节点,就算我们在这里要在第一个元素之前插入一个节点,也跟正常插入操作没有区别,第一个头节点的特殊性已经被我们的虚拟头节点弱化了,所有节点平级。在执行插入操作时,我们只需要把需要把虚拟头节点的next给要插入的节点的next,然后再把新插入的节点放在虚拟头节点的后面,这样就完成插入操作,这里需要注意的是顺序问题,避免覆盖操作,因为一个节点只能记录一个位置,一般是从右往左进行操作。

先连后面,再连前面

新节点先连旧头 → 虚拟头再连新节点


void addAtTail(int val)

将一个值为 val 的节点追加到链表中作为链表的最后一个元素。

题目解析:

插入到链表的最后一个元素的后面

复制代码
head(虚拟头) → 节点1 → 节点2 → 节点3 → null
                             ↑
                       最后一个节点:节点3
  • 节点 3 是最后一个节点
  • 它的 next 指向 null
  • null 不是节点,只是标记 "后面没了"

因此我们在操作的时候,只需要找哪个节点的值等于null即可,for循环遍历,找到之后直接把cur.next给新节点,null往后移动。

复制代码
ListNode cur = head;

head → 20 → 10 → null
cur

循环:while (cur.next != null)

  • cur.next = 20 != null → cur = 20
  • cur.next = 10 != null → cur = 10
  • cur.next = null → 停

void addAtIndex(int index, int val)

将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。

题目解析:

我们先上来校验index是否合法

之后我们就开始处理一般操作,先要找到要插入节点的前驱节点,不然插不进去,会断链。

逐行代码流程演示

我们用这个链表当例子:

plaintext

复制代码
head(虚拟头) → 10 → 20 → 30 → null
size = 3
index 编号:      0    1    2

现在执行:

复制代码
addAtIndex(1, 99);

意思:在 index=1 的位置插入 99


第 1 步:判断 index 合法

复制代码
if (index < 0 || index > size) {
    return;
}

index=1,在 0~3 之间 ✅ 合法


第 2 步:找 前驱节点

复制代码
ListNode pre = head;
for (int i = 0; i < index; i++) {
    pre = pre.next;
}

我们要插 index=1,所以循环 跑 1 次

流程:

  • 开始 pre = head

  • i=0 → pre = pre.next → pre 指向 10

前驱找到了:10


第 3 步:创建新节点

复制代码
ListNode newNode = new ListNode(99);

第 4 步:插入(和头插法一样)

第一步:新节点先连后面

复制代码
newNode.next = pre.next;

pre.next 是 20所以:

plaintext

复制代码
99 → 20

第二步:前驱再连新节点

复制代码
pre.next = newNode;

plaintext

复制代码
10 → 99

最终链表变成

plaintext

复制代码
head → 10 → 99 → 20 → 30 → null
index: 0    1    2    3

第 5 步:长度 + 1

复制代码
size++;

size 从 3 → 4


void deleteAtIndex(int index)

如果下标有效,则删除链表中下标为 index 的节点。

题目解析:

因为有虚拟节点,不需要对index=0特殊处理

循环跑 index 次 → 找到要删除节点的前驱

  • 删除 index=0 → 循环跑 0 次 → pre = head
  • 删除 index=1 → 循环跑 1 次 → pre 指向 index=0
  • 删除 index=2 → 循环跑 2 次 → pre 指向 index=1

删除 = 找前驱 → 跳过要删的节点 找前驱 = 用 for 循环跑 index 次

结语:

如果对你有帮助,请**点赞,关注,收藏,**你的支持就是我最大的鼓励!

相关推荐
灵感__idea4 小时前
Hello 算法:“走一步看一步”的智慧
前端·javascript·算法
lwf0061645 小时前
导数学习日记
学习·算法·机器学习
头发够用的程序员6 小时前
从滑动窗口到矩阵运算:img2col算法基本原理
人工智能·算法·yolo·性能优化·矩阵·边缘计算·jetson
武帝为此6 小时前
【数据清洗缺失值处理】
python·算法·数学建模
Halo_tjn7 小时前
Java 基于字符串相关知识点
java·开发语言·算法
念越7 小时前
算法每日一题 Day08|双指针法解决三数之和
算法·力扣
黎阳之光8 小时前
黎阳之光透明管理:视频孪生重构智慧仓储新范式
人工智能·算法·安全·重构·数字孪生
6Hzlia8 小时前
【Hot 100 刷题计划】 LeetCode 199. 二叉树的右视图 | C++ DFS 逆序遍历
c++·leetcode·深度优先
CappuccinoRose9 小时前
回溯法 - 软考备战(四十三)
算法·排列组合·路径·n皇后·子集·解数独·岛屿
AC赳赳老秦9 小时前
OpenClaw进阶技巧:批量修改文件内容、替换关键词,解放双手
java·linux·人工智能·python·算法·测试用例·openclaw