Leetcode链表篇总结(C++)

2023-10-27


注:按照代码随想录的刷题指南进行,自己总结补充,以加深印象
参考链接:https://leetcode-cn.com/circle/article/wGp7Y9/
题目来源:力扣(LeetCode)

一、基础知识

1、链表的定义
单链表:(leetcode上的实现)

 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };

其中不定义构造函数也可以,但c++默认的构造函数并不能初始化任何成员值,需要new之后额外赋值。

2、存储方式
在内存中并不会额外存储

3、分类
单链表,双链表,循环链表

4、与数组的关系
数组:适合数据量固定、较多查询、较少增删的情况。
链表:适合数据量不固定、较少查询、较多增删的情况。

5、注意
**删除链表元素时,在C++里最好是再手动释放这个D节点,释放这块内存。**其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了

二、经典题目

1、 203:移除链表元素(简单)

题目:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

示例:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

关键点:虚拟头节点方便链表节点的删除(因为带虚拟头节点不需要再站专门对头节点的删除进行判断,逻辑简单,提交结果表明运行时间提高很大。但需注意,返回时应返回直接时的头节点位置!dummyhead->next)
复杂度:空间复杂度O(1),时间O(n)

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
            ListNode * dummyhead = new ListNode(val-1,head);//虚拟头节点
            ListNode * p = dummyhead;//当前处理节点的前驱结点
            ListNode *q  = nullptr; 
            while(p->next != nullptr){
                if((p->next)->val == val){
                     q = p->next;
                    p->next = p->next->next;
                     delete q;  //释放内存
                }else{
                    p = p->next;
                }
            }
            return dummyhead->next;
    }

解法2:递归法
基本思想: 递归法,从上到下如果当前节点不是val,则向上一层返回自己的指针;否则返回自己的后继节点的指针。
分析:时间复杂度是O(n)。递归的方法调用栈深度是n,所以空间复杂度O(n)

	ListNode* removeElements(ListNode* head, int val) {
            if(head == nullptr)
                return nullptr;
             head->next=removeElements(head->next,val);
            if(head->val==val){
                return head->next;
            }else{
                return head;
            }

// 作者:lewis-dXStAbdZEw
// 链接:https://leetcode-cn.com/problems/remove-linked-list-elements/solution/203yi-chu-lian-biao-yuan-su-by-lewis-dxstabdzew/

2、707-设计链表(中等)

题目:

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

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

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

示例:

MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2); //链表变为1-> 2-> 3
linkedList.get(1); //返回2
linkedList.deleteAtIndex(1); //现在链表是1-> 3
linkedList.get(1); //返回3

思路:五个函数覆盖了链表的常见操作,是练习链表操作非常好的一道题目
解法1:单链表

class MyLinkedList {
    //在这个类中,封装了单链表的数据结构和其上的操作
public:
    struct Node{
        int val;
        Node * next;
        Node(int val ):val(val), next(nullptr){};
        Node(int val , Node* next):val(val), next(next){}
    };

    MyLinkedList() {
        //虚拟节点方便计算
        _dummyHead = new Node(0); 
        _maxindex = -1;
    }
    
    int get(int index) {

        if(index < 0 || index >_maxindex)
            return -1;
        Node * cur = _dummyHead;
        for(int i = 0; i <= index; i++){
            cur = cur->next;
        }
        return cur->val;
        
        
    }
    
    void addAtHead(int val) {
        Node * newNode = new Node(val, _dummyHead->next);
        _dummyHead->next = newNode;
        _maxindex++;
    }
    
    void addAtTail(int val) {
        Node * cur = _dummyHead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        Node * newNode = new Node(val);
        cur->next = newNode;
        _maxindex++;
    }
    
    void addAtIndex(int index, int val) {
        if(index == _maxindex +1 )
            addAtTail(val);
        else if(index > _maxindex+1)
            return ;
        else{
            Node * cur = _dummyHead;
            for(int i = 0 ; i < index; i++){
                cur = cur->next;
            }
            Node * newNode = new Node(val);
            newNode->next = cur->next;
            cur->next = newNode;
            _maxindex++;
        }
        
    }
    
    void deleteAtIndex(int index) {
        if(index < 0 || index > _maxindex)
            return ;
        Node * cur = _dummyHead;
        for(int i = 0 ; i < index; i++){
            cur = cur->next;
        }
        Node * p = cur->next;
        cur->next = p->next;
        delete p;
        _maxindex--;


    }
   
private:
    int _maxindex;//链表的最大索引值
    Node * _dummyHead;
};
/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

解法2:双链表
其结构复杂,但优点是在插入节点时,可以通过比较index和index_size来判断是从头结点或者尾结点来查询;双链表同样使用虚拟头节点和虚拟尾结点来简化插入和删除。
双链表节点

struct ListNode{
    int val;
    ListNode* prev, * next;
}

3、206-反转链表(简单)

题目描述
**基本思想:双指针实现,将两个节点间的link反转,**参考链接的动图https://github.com/youngyangyang04/leetcode-master/blob/master/problems/0206.%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.md
解法1:双指针解法
注意点:

    // ListNode * prev = head;
    // ListNode * cur = head->next;
  最开始赋值错误,cur不能直接是第二个节点,这样节点1的next没有改变,则构成了环
 ListNode* reverseList(ListNode* head) {
        if(head == nullptr)
            return nullptr;
        //赋值错误,cur不能直接是第二个节点,这样节点1的next没有改变,则构成了环
        // ListNode * prev = head;
        // ListNode * cur = head->next;
        ListNode * cur = head;
        ListNode * prev = nullptr;
        ListNode * p = nullptr;
        while(cur != nullptr){
            p = cur->next;
            cur->next = prev;
            prev = cur;
            cur = p;
        }
        return prev;
    }

解法2:递归解法,逻辑同双指针

 ListNode * reverse(ListNode * prev, ListNode * cur){
        if(cur == nullptr)
            return prev;
        ListNode * p = cur->next;
        cur->next = prev;
        
        return reverse(cur, p);   //最终函数结果为递归最后一层返回的值
    }
    ListNode* reverseList(ListNode* head) {
        
        ListNode * cur = head;
        ListNode * prev = nullptr;
        
        return reverse(prev, cur);
    }

4、142-环形链表(中等)

在这里插入图片描述
简单解法:用hash表存储每一个节点,判断是否重复;nordered_set<ListNode *> visited;

解法2:双指针,快指针一次两步,慢指针一次一步,如果有环,两指针一定在环中相遇
关键点:数学公式推导
1、如何判断相遇?在无环情况下,快慢指针永远不会相遇,所以相遇点一定在环上
2、为何慢指针第一圈走不完一定会和快指针相遇:当慢指针刚到达环的入口时,快指针此时在环中的某个位置,设环的周长为n,两者距离x>=0;那么看成快指针追赶慢指针,需要追赶n-x; 以慢指针为参考系,快指针相当于每次追赶1步。那么追赶需要(n-x)s ,则慢指针走的路程小于等于n,即走不完一圈就和快指针相遇
3、如何判断相遇点: 图片来自代码随想录
在这里插入图片描述
相遇时: slow 经过的节点数 x+y; fast的节点数 x + n(y+z) + y ;
2 * (x+ y ) = x + y + n(y+z),
其中n为fast指针循环的圈数,由分析2可知n = 1, 所以x = z
所以两个指针, 分别从相遇点和head节点出发,再次相遇的节点便是环的入口
代码:


class Solution {
 
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode *fast, * slow;
        fast = slow = head;

        while(fast != NULL && fast->next != NULL){
            fast = fast->next->next;//两步
            slow = slow->next;
            //两者相遇
            if(fast == slow){
                slow = head;
                //根据公式x = (n-1)(y+z)+z, 从相遇点和首节点开始循环,最终相遇点为环的入口
                while(slow != fast){
                    slow = slow->next;
                    fast = fast->next;
                }
                return slow;
            }
        }
        return NULL;
    }
};

5、19-删除链表的倒数第N个结点(中等)

在这里插入图片描述
思想1:首先想到用递归在fun函数体内犯了一个错误,直接写了return a++, 那么++写在数值后面,那么返回值就都是1。官方题解里还直接用栈存储所有结点,然后再pop出去,第N个就是要删除的节点,和递归思想类似。
分析:时间复杂度O(n)

class Solution {
public:
    int fun(ListNode * head, int n){
        if(head->next == nullptr){
            return 1;
        }

        int a = fun(head->next, n);
        //cout<<a<<endl;
        if(a == n){
            ListNode * p = head->next;
            head->next = p->next;
            delete p;
        }
        a = a + 1;
        return a;
        
    }
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode * dummyHead = new ListNode();
        dummyHead->next = head;
        int a = fun(dummyHead, n);
        //cout<<a;
        return dummyHead->next;
    }
};

思想2:双指针,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow的后继节点即可。
分析:只需要遍历一次,时间复杂度O(n)

 ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode * dummyHead = new ListNode();
        dummyHead->next = head;
        ListNode * fast, * low;//最终low指向倒数第N个节点的前驱节点,fast指向最后一个节点
        fast  = dummyHead;
        low = dummyHead; 
        int i = 0;
        while(i < n && fast != nullptr){
            fast = fast->next;
            i++;
        }
        while(fast->next != nullptr){
            low = low->next;
            fast = fast->next;
        }
        ListNode * p = low->next;
        low->next = low->next->next;
        delete p;
        
        return dummyHead->next;
    }

6、面试题 02.07. 链表相交(简单)

题目描述:
在这里插入图片描述
思路1:将两个链表从尾部对齐,指针指向两者头部对齐的位置,一齐向后遍历,查看指针是否相等
注意点: 注意边界!
分析:时间复杂度O(lenA + lenB),空间O(1)

node1 = lenA > lenB ? headA : headB; 
//之前写的是lenA <lenB, 忽略了相等的情况,答案错误
node2 = lenA <= lenB ? headA : headB;
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    int lenA = 0, lenB = 0;
    ListNode * node1,* node2 ;
    
    //计算A的长度
    node1 = headA;
    while(node1 != NULL){
        lenA++;
        node1 = node1->next;
    }
    //计算B的长度
    node1 = headB;
    while(node1 != NULL){
        lenB++;
        node1 = node1->next;
    }
    //分别指向链表对齐的地方,其中node1需要提前向前
    node1 = lenA > lenB ? headA : headB; 
    node2 = lenA <= lenB ? headA : headB;//之前写的是lenA <lenB, 忽略了相等的情况,答案错误
    for(int i = 0; i < abs(lenA - lenB); i++){
        node1 = node1->next;
    }
    while(node1 != NULL && node2 != NULL){
        if(node1 == node2){
            return node1;
        }
        node1 = node1->next;
        node2 = node2->next;
    }
    return NULL;


}

思路2:刚开始就被极短的代码量惊到了,参考链接(https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/solution/mian-shi-ti-0207-lian-biao-xiang-jiao-sh-b8hn/)
即nodeA遍历完A后再遍历B ; nodeB遍历B完后遍历A;如果两个链表有交点,那么必然会在第一个公共交点处相遇, 否则最终都在链表结尾null相遇。
数学原理: 设链表A的长度为a, B的长度为b, 公共长度为c
当走到第一个公共交点时,nodeA所走路径: a+ (b-c); 同理,nodeB所走路径为b+(a-c) ; 两者相等,即为相遇 a+ (b-c) = b +(a-c)
分析:时间复杂度O(lenA + lenB),空间O(1)

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode * nodeA, * nodeB;
        nodeA = headA;
        nodeB = headB;
        while(nodeA != nodeB){
            nodeA = nodeA != NULL ? nodeA->next : headB;
            nodeB = nodeB != NULL ? nodeB->next : headA;
        }
        return nodeA;
 }

思路3:用堆栈思想,从尾部开始判断,但显然空间复杂度较高O(lenA+lenB)

三、总结

1、链表的处理通常考虑添加虚拟节点来简化逻辑操作

2、常见数值范围与对应十进制数值

类型 二进制范围 十进制范围
int -231~(2 31-1) 109数量级
unsigned int 0~(2 32-1) 109数量级
long int -263~(2 63-1) 1018数量级
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Leetcode链表篇总结(C++) 的相关文章

  • 计算机网络思维导图

    计算机网络思维导图 按照计算机网络的TCP IP四层模型构建 物理层对应网络接口层的功能 这个是之前期末考试时做的思维导图 现在复习专业课发现里面还有很多缺漏 希望大家多多提意见 后期有时间我也会查漏补缺的 谢谢支持 目录 计算机网络思维导
  • 集成运算放大电路实验报告_模电总结:第三章、集成运算放大电路

    我的公众号 每日晴天 可关注领取我的笔记pdf版哦 部分英文 d差模 different 差模的d c 共模 common mode 3 1多级放大电路 1 多级放大电路的组成 输入级 输入电阻高 噪声和漂移小 中间级 具有足够大的放大能力
  • android httpClient 支持HTTPS的2种处理方式

    项目中Android https或http请求地址重定向为HTTPS的地址 相信很多人都遇到了这个异常 无终端认证 javax net ssl SSLPeerUnverifiedException No peer certificate 1
  • YoloV4训练自己的数据集

    YoloV4训练自己的数据集 1 建立工作文件夹 2 准备训练数据集 3 修改配置文件 4 Anchor Box先验框聚类分析与修改 5 训练自己的数据集 6 测试训练出来的网络模型 7 性能统计 1 建立工作文件夹 新建一个项目文件夹 用

随机推荐

  • STM32/AMP32F407进入低功耗待机模式后立马被唤醒的解决办法

    最近项目用到低功耗 但是调试发现进入待机就被唤醒的问题 清除WU SB两个唤醒标志位 也依然被立马唤醒一次 进入待机模式 HAL PWR EnterSTANDBYMode 通过极海的数据手册终于发现 我们在使能WAKE UP PA 0唤醒脚
  • 【Linux】进程描述符

    linux进程管理 1 进程描述符 进程描述符 Linux使用进程描述符数据结构记录现场信息 然后给予进程描述符管理进程 包括进程的创建 调度 消亡等操作 进程除了包括运行着的程序 还包括系统资源 当前CPU现场 调度信息 进程间关系等 记
  • One PUNCH Man——激活函数和梯度消失/爆炸

    文章目录 什么是激活函数 激活函数介绍 梯度消失 爆炸 首先推荐一个写公式的网站 https private codecogs com latex eqneditor php 什么是激活函数 如下图 在神经元中 输入的 inputs 通过加
  • FlatBuffers在Java中的使用

    1 去maven仓库下载官网库flatbuffers java 1 7 0 1 jar 地址 点击打开链接 2 编写fbs文件 chat fbs namespace Proto 聊天频道 enum ChatChannel byte SYST
  • 利用Logstash plugins做更多的事情

    1 引言 之前一篇文章 Logstash 介绍及linux下部署 我们实现了logstash的安装以及简单的控制台标准输入输出测试 那么logstash能不能做更多的事情呢 答案是肯定的 logstash就是为了处理日志数据而生的 一个最直
  • mysql下出现Unknown column 'xx' in 'on clause'的完全解决方法

    mysql下出现Unknown column xx in on clause 的完全解决方法 在项目中执行查询无结果 在数据库运行sql报错 Unknown column xx in on clause 百度过后找到这个文章并完全解决了问题
  • 欠拟合和过拟合

    过拟合 定义 具体表现就是最终模型在训练集上效果好 在测试集上效果差 模型泛化能力弱 具体表现就是最终模型在训练集上效果好 在测试集上效果差 模型过于复杂 过拟合的原因 训练数据中噪音干扰过大 使得学习器认为部分噪音是特征从而扰乱学习规则
  • Nginx二级项目配置

    Nginx二级项目服务页面部署 应用场景 比如一个项目需要一个正常的生产服务的地址在根目录 现在需要一个后台管理项目可以部署在 admin 的路径下 需要帮助文档的页面可以部署在 help nginx conf 配置 location ma
  • 【SQL注入-08】二次注入案例—以sqli-labs-less24为例

    目录 1 二次注入概述 1 1 定义 1 2 思路 2 二次注入案例 2 1 实验平台 2 2 实验目标 2 3 具体注入过程 2 3 1 注入前准备 2 3 2 确定目标账户admin 2 3 3 第一次注入 2 3 4 第二次注入 2
  • eos bp节点 超级节点搭建

    https github com nebulaprotocol 这个网址里面有一个 fake terminal website 比较有意思 可以看看示例 https bp nebulaprotocol com 搭建eos BP节点 环境搭建
  • 家政服务小程序制作攻略揭秘

    想要打造一个家政服务小程序 但是又不懂编程和设计 不用担心 下面将为你详细介绍如何利用第三方平台 从零开始打造一个家政服务小程序 首先 你需要找到一个适合的第三方平台 例如乔拓云网 在乔拓云网的 轻应用小程序 中 点击 去管理 你就可以进入
  • 回顾2021,展望2022

    2021 这一年最大的收获是孕育了一个聪明漂亮机灵的小家伙 这一年我虚岁28岁 和爱的人有了爱的结晶 东哥各方面都挺好的 我们都不是圣人 都是能力有限的普通人 但他在尽其所能的对我好 我不是万能的人 但也独立坚强 之后一个人带娃的日子适应的
  • LaTeX公式中指定某些字母或符号为正体

    m rm G 显示效果为
  • I/O 函数/缓存和字节流、占位符、getchar(),putchar()

    I O 函数 C 语言提供了一些函数 用于与外部设备通信 称为输入输出函数 简称 I O 函数 输入 import 指的是获取外部数据 输出 export 指的是向外部传递数据 缓存和字节流 严格地说 输入输出函数并不是直接与外部设备通信
  • 从零开始学C++之异常(三):异常与继承、异常与指针、异常规格说明

    一 异常与继承 如果异常类型为C 的类 并且该类有其基类 则应该将派生类的错误处理程序放在前面 基类的错误处理程序放在后面 include
  • python3.5源码分析-启动与虚拟机

    Python3源码分析 本文环境python3 5 2 参考书籍 lt
  • 虚幻引擎游戏开发过程中,游戏鼠标如何双击判定?

    UE虚幻引擎对于游戏开发者来说都不陌生 市面上有47 主机游戏使用虚幻引擎开发游戏 作为是一款游戏的核心动力 它的功能十分完善 囊括了场景制作 灯光渲染 动作镜头 粒子特效 材质蓝图等 本文介绍了虚幻引擎游戏开发过程中游戏鼠标双击判定 一起
  • 数据库变更管理:Liquibase or Flyway

    从零打造项目 系列文章 工具 比MyBatis Generator更强大的代码生成器 ORM框架选型 SpringBoot项目基础设施搭建 SpringBoot集成Mybatis项目实操 SpringBoot集成MybatisPlus项目实
  • vsftpd默认值

    原文地址 http vsftpd beasts org vsftpd conf html 国内解释 http hi baidu com 346430756 blog item 5527e9363402652a0b55a9d0 html VS
  • Leetcode链表篇总结(C++)

    文章目录 一 基础知识 二 经典题目 1 203 移除链表元素 简单 2 707 设计链表 中等 3 206 反转链表 简单 4 142 环形链表 中等 5 19 删除链表的倒数第N个结点 中等 6 面试题 02 07 链表相交 简单 三