🔥个人主页:北极的代码(欢迎来访)
🎬作者简介: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 个节点。

707示例

题目答案:

//单链表
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 次

    结语:

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

    Logo

    AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

    更多推荐