代码随想录训练营Day 03 | 链表Part 01(8.30)

一、链表理论基础

链表的类型:

①单链表

②双链表

③循环链表

链表的存储方式:

前两天学习的数组,数组在内存中是连续分布的,但是链表在内存中并不是连续分布的,链表在内存中是通过指针域的指针链接各个节点。

而链表的定义,这一步定义,采用了结构体的形式进行定义,同时结构体中采用了节点的构造函数。在C++中,它会默认生成一个构造函数,但是默认生成的构造函数不会初始化任何的成员变量,但是在结构体中进行函数的构造就可以手动进行初始化。

例如:

struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

二、相关的链表操作

删除节点

在删除节点的时候需要注意:更改了节点的指针指向后,其实只是让特定的节点不在存储在链表里而已,C++中需要手动释放节点从而释放空间,但Java,Python有自己的内存回收机制,所以无需手动释放。

添加节点

三、题目解析

(一)203.移除链表元素

题目链接: 203. 移除链表元素 - 力扣(LeetCode)

文章链接:代码随想录 (programmercarl.com)  

视频链接: 手把手带你学会操作链表 | LeetCode:203.移除链表元素_哔哩哔哩_bilibili

操作链表删除有两个方式:

①使用原链表删除元素  ②设置一个虚拟头节点

(1)使用原链表来删除元素

利用这一种方法在移除元素的时候,就需要分开进行处理,需要考虑当前要移除的元素是否是头节点,因为头节点的前面是没有节点的,所以需要考虑这一点。因此也就有了后续会提到的第二种方法:设置一个虚拟头节点。

那么,头节点究竟如何进行移除呢?头节点的移除,其实就只需要将头节点后移一位,让没有任何指针指向头节点,也就让本来的头节点不在链表中了,也就是head = head->next; 但在这之前,还是需要定义一个临时变量用来存储原本的“头节点”,以便于进行节点的释放,节省空间。

使用原链表代码

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        while(head != NULL && head->val == val) {
            ListNode* t;
            t = head;
            head = head->next;
        }
        ListNode *current = head;
        while (current != NULL && current->next != NULL) {
            //注意,删除最后一个节点这里也能实现,注意看if里的条件
            if (current->next->val == val) {
                ListNode *t = current->next;
                current->next = current->next->next;
                delete t;
            }
            else {
                current = current->next;
            }
        }
        return head;
    }
};

(2)设置一个虚拟头节点来移除元素

这个方法的意思就是,在原本的头节点前面再增加一个“临时头节点”,这样就能统一删除的方法,不需要再考虑当前的“节点”还是不是“头节点”,但是这里面存在一个易错点,就是当我们完成元素的移除后,我们习惯性的可能会返回head,但是原本的head在一定概率上会被删除,也就是原本的head其实不一定存在了,但是与此同时,我们又该如何返回一个新链表?以及删除、释放掉我们最开始所设置的虚拟头节点?

其实不难想到,我们要返回的是虚拟头节点的next,但是如果直接以这种形式返回的话,就没办法做到释放虚拟头节点,在这种情况下的解决办法就是将虚拟头节点的next赋值给head,然后进行释放节点,释放完以后便可以采用return head来返回新的链表。

设置虚拟头节点代码

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* p = new ListNode(0);
        p->next = head;
        ListNode *t = p;
        while(t->next != NULL) {
            if(t->next->val == val) {
                t->next = t->next->next;
            }
            else {
                t = t->next;
            }
        }
        head = p->next;
        delete p;
        return head;
    }
};

这一题的代码在写的时候还是出现了一些问题,但是还是出现了一些问题,例如,我需要先设定一个新的变量才存储所设置的临时节点,以便于后续的删除,还有一个则是进行更新遍历的变量。

(3)其他

面对这一题,还有一种方法,应该也有不少人想到过,也就是递归。在创建树,创建节点等等很多情况下都会运用到递归函数,但是个人认为它比较消耗时间,所以针对本题并没有深入地去自己写,但可以参考如下代码。

递归函数写法:

来源:代码随想录代码随想录 (programmercarl.com)

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 基础情况:空链表
        if (head == nullptr) {
            return nullptr;
        }

        // 递归处理
        if (head->val == val) {
            ListNode* newHead = removeElements(head->next, val);
            delete head;
            return newHead;
        } else {
            head->next = removeElements(head->next, val);
            return head;
        }
    }
};

(二)707.设计链表

这一题整体的代码就略微多一点,但是涉及到了链表的大部分操作,显得也就比较重要。

题目链接:707. 设计链表 - 力扣(LeetCode)

文章链接:代码随想录 (programmercarl.com)

视频链接:帮你把链表操作学个通透!LeetCode:707.设计链表_哔哩哔哩_bilibili

思路描述及代码块

在类中,主要实现的功能是,初始化对象,获取特定下标值的节点的值,在头节点前插入一个节点,在尾节点后插入一个节点,在第n个节点之前插入一个节点,删除下标为特定值的节点。

①初始化对象

    MyLinkedList() {
        dum = new LinkedNode(0);
        size = 0;
    }

②获取链表下标为index的节点的值

    int get(int index) {
        if (index > size -1 || index < 0) {
            return -1;
        }
        else {
            LinkedNode *cur = dum->next;
            while(index--) {
                cur = cur->next;
            }
            return cur->val;
        }
    }

③将一个值为val的节点插入到链表中的第一个元素之前

④将一个值为val的节点作为该链表的最后一个节点

⑤将一个值为val的节点插入到链表下标为index的节点之前

⑥删除下标为index的节点

这一题的代码我在力扣上没提交,因为测试中一直报错emmm后续再研究研究

(三)206.反转链表

题目链接:206. 反转链表 - 力扣(LeetCode)

文章链接:代码随想录 (programmercarl.com)

视频链接:

帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法_哔哩哔哩_bilibili

这一题的难点在于,反转的思路及其节点之间的搭建过程。节点的反转,就是将头节点变成尾节点,而尾节点变成头节点,我认为最难的就是处理节点之间的联系以及边界的处理。同时,这一题仿佛又用到了前两天所学到的“双指针”,这一题的双指针也和前两天的又有所不同了。

这一题的双指针,主要是用在了一前一后的指向,以便于改变链表之间的连接关系,还有一个就是代码的循环中代码怎么写。

 图中

①为先断掉cur的next,但需要提前将原cur->next存储起来

②为更改cur的next

③④为移动指针

该思路代码如下:

        while(cur) {
            t = cur->next;
            cur->next = pre;
            pre = cur;
            cur = t;
        }

提交通过代码:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *cur = head,*pre = NULL ,*t;
        while(cur) {
            t = cur->next;
            cur->next = pre;
            pre = cur;
            cur = t;
        }
        return pre;
    }
};
  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值