【算法练习Day3】 移除链表元素&&设计链表&&反转链表

2023-09-22 15:18:40

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:练题
🎯长路漫漫浩浩,万事皆有期待

移除链表元素

203. 移除链表元素 - 力扣(LeetCode)

链表问题大多都可以用虚拟头结点的方法,使链表的插入和删除操作变得简单。

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dunnmyhead=new ListNode(0);// 设置一个虚拟头结点
        dunnmyhead->next=head;// 将虚拟头结点指向head,这样方面后面做删除操作
        ListNode* cur=dunnmyhead;
        while(cur->next!=nullptr)
        {
            if(cur->next->val==val)
            {
                ListNode* tmp=cur->next;
                cur->next=cur->next->next;
                delete tmp;
            }
            else
            {
                cur=cur->next;
            }
        }
        head=dunnmyhead->next;
        delete dunnmyhead;
        return head;
    }
};

这道题并不难,值得注意的是,创建一个临时指针,指向head,然后判断它的值是否等于val,要注意的是用cur->next->val来判断值是否是我们需要找的,如果是的话,cur指向的正是要删除节点的上一个节点,直接将cur的next指针连到要删除节点的下一个位置就可以了

其他问题

注意:指针问题, 写删除链表题的时候经常少了else判断, 链表首要想好指针是怎么移动的,是否会访问null即可

● 对于链表问题可以多用笔画画图,这样会加深对指针和节点实体的理解,代码的鲁棒性如何通常可以利用边界case尝试,很多都是因为空指针的错误,手动画图走一遍自己的代码一般就能发现。

● 是否要添加虚拟头结点 :
虚拟头结点的主要目的是为了避免对头结点的特殊处理;这个处理就指的是修改操作。所以可以这样:涉及到对链表修改(如插入,删除,移动)的,都加个dummy,只是遍历取点就可以不用加

设计链表

707. 设计链表 - 力扣(LeetCode)

设计链表是一道综合题,涵盖了很多有关于链表的函数接口,适合练习,整体思路不算很难。

class MyLinkedList {
public:
	// 定义链表节点结构体
    struct Listnode
    {
        int val;
        Listnode* next;
        Listnode(int val)
            :val(val)
            ,next(nullptr)
        {

        }
    };

	// 初始化链表
    MyLinkedList() {
        _size=0;
        _dummyHead=new Listnode(0);// 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点

    }
    // 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
    int get(int index) {
        if(index<0||index>(_size-1))
        {
            return -1;
        }
        Listnode* cur=_dummyHead->next;
        while(index--) // 如果--index 就会陷入死循环
        {
            cur=cur->next;
        }
        return cur->val;

    }
     // 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
    void addAtHead(int val) {
        Listnode* newnode=new Listnode(val);
        newnode->next=_dummyHead->next;
        _dummyHead->next=newnode;
        _size++;

    }
    // 在链表最后面添加一个节点
    void addAtTail(int val) {
        Listnode* newnode=new Listnode(val);
        Listnode*cur=_dummyHead;
        while(cur->next)
        {
            cur=cur->next;
        }

        cur->next=newnode;
        _size++;

    }
     // 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果index大于链表的长度,则返回空
    // 如果index小于0,则在头部插入节点
    void addAtIndex(int index, int val) {
        if(index>_size)
        {
            return ;
        }
        if(index<0)
        {
            index=0;
        }
        Listnode* newnode=new Listnode(val);
        Listnode* cur=_dummyHead;
        while(index--)
        {
            cur=cur->next;
        }
        newnode->next=cur->next;
        cur->next=newnode;
        _size++;

    }
     // 删除第index个节点,如果index 大于等于链表的长度,直接return,注意index是从0开始的
    void deleteAtIndex(int index) {
        if(index<0||index>(_size-1))
        {
            return ;
        }
        Listnode* cur=_dummyHead;
        while(index--)
        {
            cur=cur->next;
        }
        Listnode* tmp=cur->next;
        cur->next=cur->next->next;
        delete tmp;
         //delete命令指示释放了tmp指针原本所指的那部分内存,
        //被delete后的指针tmp的值(地址)并非就是NULL,而是随机值。也就是被delete后,
        //如果不再加上一句tmp=nullptr,tmp会成为乱指的野指针
        //如果之后的程序不小心使用了tmp,会指向难以预想的内存空间
        tmp=nullptr;
        _size--;        
    }

private:
    int _size;
    Listnode* _dummyHead;
};

起初写的时候很懵,还在想为什么没有给头节点指针,而是只有index?后来看题解才知道要自己创建结构体,和虚拟头节点(方便空链表插入)。整体思路难度适中,只是接口多了一些,思路清晰还是能写出得出来的。

其他问题

● 设计链表因为调用了很多函数,一旦出错可能不知道调试从何下手,因此建议把代码放到本地IDE上,一个一个函数去测试看出错在什么地方

● 对于第index个有所混淆,做题目前一定要理解题目的意思,这里的index是从0开始的

● addAtIndex 方法中描述的“如果 index 等于链表的长度,则该节点将附加到链表的末尾” 这句话中 链表长度这里是指 size而不是size - 1,如果是size-1的话那就会和在最后一个元素前插入冲突了

反转链表

206. 反转链表 - 力扣(LeetCode)

思路明确的同时还要注意head指针传进来时,若是空的情况要怎么处理,以及应该创建几个临时节点,分别保存什么。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* tmp=nullptr;// 保存cur的下一个节点
        ListNode* cur=head;
        ListNode* pre=nullptr;
        while(cur)
        {
            tmp=cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur->next=pre;// 翻转操作
            pre=cur; // 更新pre 和 cur指针
            cur=tmp;
        }
        return pre;
    }
};

注意:更新pre 和 cur指针时,先让pre=cur,否则,cur指向的节点就丢失了

创立三个临时指针,一个用来保存要反转的节点的下一个链接节点,一个用来指向下一个反转节点的next连接到哪里,另一个代表当前要反转的节点。缺一不可的,一开始pre要指向null,使第一个反转的链表有指向的指针,只要明确要用到几个指针,以及分别初始化为什么值,这道题就解的出来了。

递归代码实现,和上面的方法思路是相同的。

class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur)
    {
        if(cur==nullptr)
        {
            return pre;
        }
        ListNode* tmp=cur->next;
        cur->next=pre;
        // 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
        // pre = cur;
        // cur = temp;
        return reverse(cur,tmp);
    }
    ListNode* reverseList(ListNode* head) {
        // 和双指针法初始化是一样的逻辑
        // ListNode* cur = head;
        // ListNode* pre = NULL;
       return reverse(nullptr,head);
    }
};

其他问题

● 链表一定要分清节点和指针的概念。 new ListNode()是真实存在的一个节点, head = new ListNode() 相当于 head指针指向了一个真实的节点, node = head, 相当于node和head同时指向了这个真实的节点

● 尽量不要去动虚拟头节点,因为虚拟头节点本来就是个工具节点,操作后面的节点本身就好

● 为什么有时候需要判定cur->next !=nullptr 有时候又不需要 只用判断cur!=nullptr呢?
其实这个要看场景,一般就是看你如果不判断的话会不会导致空指针异常,因为如果你的指针遍历到null还调用它的属性或方法肯定就会报错的,但是有时候的情况不会遍历到cur->next,所以要看具体情况。

总结:

今天的三道题虽然之前做过,但从循环到递归的转变感觉第一次非常清晰的领悟了。接下来,我们继续进行算法练习·。希望我的文章和讲解能对大家的学习提供一些帮助。

当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~

在这里插入图片描述

更多推荐

LeetCode每日练习之链表常见题目

1.两个链表的第一个公共节点输入两个链表,找出它们的第一个公共节点。1.1思路哈希和集合,先将一个链表全部存到Map里,然后一边遍历第二个链表,一边检测Hash是否存在当前结点,如果有交点,那么一定能检测出来,使用两个栈,分别将两个链表入栈,然后分别出栈对比,如果相等就出栈,知道找到最晚出栈的那组,拼接两个字符串,将两

【论文记录】Boosting Detection in Crowd Analysis via Underutilized Output Features

BoostingDetectioninCrowdAnalysisviaUnderutilizedOutputFeaturesAbstractCrowdHat使用一种混合的2D-1D压缩技术进行细化空间特征与获取特定人群信息的空间和数量分布。进一步的,CrowdHat采用自适应区域的NMS阈值与一个解耦然后对齐的范式来解

猫眼 面经和答案

你好,我是田哥上一篇文章,给大家分享了几家公司的面经;最新猫眼、阿里云、美团....面经有朋友私聊我,说昨天的这篇文章中,只给出了面试题,没有答案,今天给安排一份猫眼面经的参考答案。在线刷题小程序面试题自我介绍项目用到的技术栈、项目问的比较多,一定要多看三次握手四次挥手缓存穿透和雪崩的原因和解决方法布隆过滤器你了解吗m

c++最短路计数(acwing版)

先看题目:给出一个N个顶点M条边的无向无权图,顶点编号为1到N。问从顶点1开始,到其他每个点的最短路有几条。输入格式第一行包含2个正整数N,M,为图的顶点数与边数。接下来M行,每行两个正整数x,y,表示有一条顶点x连向顶点y的边,请注意可能有自环与重边。输出格式输出N行,每行一个非负整数,第i行输出从顶点1到顶点i有多

【C++】模板初阶

今天开始将图片的水印全部去掉,以方便大家的观看和知识截屏分享,希望对大家都有所帮助模板初阶目录:一、什么是泛型编程(编写与类型无关的代码)二、函数模板2.1概念与格式2.2底层原理2.3实例化(细节较多)2.3.1隐式类型化:让编译器根据实参推演模板参数的实际类型2.3.2显示实例化2.4参数的匹配规则2.4.1尽管看

李宏毅机器学习第一课

机器学习就是让机器找一个函数f,这个函数f是通过计算机找出来的如果参数少的话,我们可以使用暴搜,但是如果参数特别多的话,我们就要使用GradientDescentRegression(输出的是一个scalar数值)Classification(在设定好的选项,两个或者多个,中做出选择)StructuredLearnin

睿趣科技:抖音开通蓝V怎么操作的

在抖音这个充满创意和活力的社交媒体平台上,蓝V认证成为了许多用户的梦想之一。蓝V认证不仅是身份的象征,还可以增加用户的影响力和可信度。但是,要在抖音上获得蓝V认证并不是一件容易的事情。下面,我们将介绍一些操作步骤,帮助你了解如何在抖音上开通蓝V认证。建立优质内容:在抖音上,内容是王道。要获得蓝V认证,首先需要建立自己的

nacos的各个客户端的功能

这两个依赖都与Nacos配置中心的集成有关,但是功能和作用略有不同。1.`com.alibaba.nacos:nacos-client`:这是Nacos的Java客户端库,用于与Nacos服务器进行通信。它提供了与Nacos的各种功能交互的API,例如获取配置、注册服务、发现服务等。这个依赖是必需的,因为它提供了与Na

RFID车辆自动化称重管理

应用背景随着物流和交通管理的发展,车辆称重成为了不可忽视的环节,传统的车辆称重管理方式存在诸多问题,如人工操作繁琐、数据准确性低、容易出现作弊等,为了提高车辆称重管理的效率和准确性,RFID技术被引入到车辆称重管理中,RFID设备实现对车辆的自动识别和数据采集,从而实现车辆自动化称重管理的目标。车辆称重管理的难点人工操

计算机网络选择题笔记

令牌环:令牌环上传输的小的数据(3个字节的一种特殊帧)叫为令牌,谁有令牌谁就有传输权限。如果环上的某个工作站收到令牌并且有信息发送,它就改变令牌中的一位(该操作将令牌变成一个帧开始序列),添加想传输的信息,然后将整个信息发往环中的下一工作站。ARP协议作用:数据帧从源节点传送到目标节点通常需要经过许多中间节点。通常,数

注意力机制代码

注意力机制(AttentionMechanism)是深度学习中常用的一种技术,用于在处理序列数据时聚焦于不同部分的信息。以下是一个简单的注意力机制示例代码,使用Python和PyTorch库实现。这个示例是一个自定义的注意力机制,可以用于文本序列的处理,例如机器翻译。首先,确保你已经安装了PyTorch库。然后,可以使

热文推荐