链表合集(easy难度)

news2025/2/3 4:05:38

合并两个有序链表

双指针法

由于list1和list2都是递增的,可以想到用双指针法。假如当前list1这个指针指向的节点被收入完成,那就list1++;如果是list2被收入,那就list2++。 

具体是list1和节点被收入还是list2的节点被收入?比较list1->val和list2->val的大小即可。

算法流程:

1. 创建dum节点(leetcode貌似都没有给虚假头节点的),作为新链表的虚假头节点。最后返回dum->next即可。

2. 写循环主体,注意循环终止的条件,list1或list2这两个指针有一个指向nullptr,说明有一个链表被选完了,那剩下另一个没被选完的直接往后插就行了。(为什么能这么干脆?因为list1和list2这两个链表本身就是有序的!)

3. 退出循环后,注意把还没选完的链表往新链表后面一插。

基于插入节点的算法设计,此处采用尾插法。

尾插法,需要用一个指针来记录尾节点。此处使用Cur

/**
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* dum = (ListNode*)malloc(sizeof(ListNode));
        ListNode* cur = dum;
        while(list1 != nullptr && list2 != nullptr) {
            if(list1->val < list2->val) {
                cur->next = list1;
                list1 = list1->next;
            }
            else {
                cur->next = list2;
                list2 = list2->next;
            }
            cur = cur->next;
        }
        if(list1 == nullptr) cur->next = list2;
        else cur->next = list1;
        return dum->next;
    }
};

递归

这直接不用创建什么dum了。

/**
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr) {
            return list2;
        }
        if(list2 == nullptr) {
            return list1;
        }
        if(list1->val <= list2->val) {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        }
        list2->next = mergeTwoLists(list1, list2->next);
        return list2;
    }
};
//这里不能再写第四个if,不然编译器觉得如果你四个条件都不满足的话,你return啥呢?

woc太深奥了。

删除有序链表中的重复元素

双指针法

用pre和cur两个指针。pre指针作为最新确定的“标杆”,cur指针始终为pre-next(写cur是为了阅读和理解方便),是我们当前需要检验的节点。

循环退出的条件是,我们没有需要检验的节点了。也就是说,cur不存在了,即cur指向nullptr。

/**
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        //链表为空或链表只有一个节点时,直接return
        //不过其实这里也可以只写链表为空的情况
        if(head == nullptr || head->next == nullptr) {
            return head;
        }
        
        ListNode* pre = head;
        ListNode* cur = pre->next;
        while(cur != nullptr) {
            if(cur->val == pre->val) {
                ListNode* r = cur->next;
                delete cur;
                pre->next = r;
                cur = r;
            }
            else if(cur->val != pre->val) {
                pre = cur;
                cur = cur->next;
            }
        }
        return head;
    }
};

这里为什么可以返回head?因为head绝对不会被删掉! 

移除链表元素

单指针迭代

单指针即可,双指针完全没必要啊。这里必须要创建虚假头节点dum!因为head可能会被删掉。注意退出循环的条件,pre后面没有东西了。因为我们每次要检验的是pre-next,那你pre后面都没东西了我还检验啥呢?

/**
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //链表为空直接返回不用犹豫
        if(head == nullptr) {
            return head;
        }
        ListNode* dum = new ListNode(0);
        dum->next = head;//dum的位置要记住,因为head可能被删掉!

        ListNode* pre = dum;//用pre走,而不用dum走
        while(pre->next != nullptr) {
            if(pre->next->val == val) {
                ListNode* r = pre->next->next;
                delete pre->next;
                pre->next = r;
            }
            else
                pre = pre->next;
        }
        return dum->next;
    }
};

递归

“链表的定义具有递归的性质”?好好好。也就是说递归法是求解链表题的常用方法是吧。其实也很好理解。

/**
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //递归终止的条件为head为空
        if(head == nullptr) {
            return head;
        }

        //递归,就是只用关注每层的工作,以及每层传递给上层的东西
        //本题,每层的工作就是把该节点之后的所有节点进行一次remove操作+对本层的头节点进行判断,以确定返回值是head还是head-next
        //所以,每层肉眼可见做的事情,只有判断本层头节点
        head->next = removeElements(head->next, val);
        //最后判断头节点
        if(head->val == val) {
            return head->next;
        }
        return head;
    }
};

反转链表

迭代法

把后继节点记住,用r表示。pre、cur进行迭代。cur是我们要处理的节点。退出循环的条件是cur为空,就是没有需要处理的节点了。

/**
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *cur = head, *pre = nullptr;
        while(cur != nullptr) {
            ListNode *r = cur->next;
            cur->next = pre;
            pre = cur;
            cur = r;
        }
        return pre;
    }
};

最初的pre是nullptr,第一个需要处理的节点cur就是头节点。( pre最初设置成nullptr作为反转前链表头节点将要指向的东西,还挺重要的。我以前好像没意识到。)

整个while循环可以直接运转,无需特殊处理。最后返回pre。

递归

/**
 * 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) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        return cur(head, nullptr);
    }
private:
    ListNode* recur(ListNode* cur, ListNode* pre) {
        if(cur == nullptr) return pre;
        
        ListNode* res = recur(cur->next, cur);
        cur->next = pre;
        return res;
    }
};

太抽象了nb。 

回文链表

先反转链表

本来是想套用上题函数,但。。。这方法有大bug!

你反转完,原来的链表不就不在啦?!所以你要用这方法就必须再复制一个链表。

所以额外还要再写两个函数,一个reverseList一个copyList。

/**
 * 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) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        //链表为空的特殊讨论
        if(head == nullptr) {
            return true;
        }

        ListNode* head1 = copyList(head);
        ListNode* head2 = reverseList(head);
        while(head1 != nullptr) {
            if(head1->val != head2->val) {
                return false;
            }
            head1 = head1->next;
            head2 = head2->next;
        }
        return true;
    }

    ListNode* reverseList(ListNode* head) {
        ListNode* cur = head, *pre = nullptr;
        while(cur != nullptr) {
            ListNode* r = cur->next;
            cur->next = pre;
            pre = cur;
            cur = r;
        }
        return pre;
    }

    ListNode* copyList(ListNode* head) {
        ListNode* newHead = new ListNode(head->val);
        ListNode* r = newHead;
        while(head->next != nullptr) {
            ListNode* newNode = new ListNode(head->next->val);
            r->next = newNode;
            r = newNode;
            head = head->next;
        }
        return newHead;
    }
};

把链表转换成数组

(我感觉可能是数据结构课上魔怔了,我的脑洞都打不开了!思维发散都不会了QAQ)

数组和链表,都是线性表的表现形式。数组是顺序表,链表是链式表。其实二者是可以相互转换的。至少在本题,回文数组可是很好求的!

我自己写的是纯数组。。因为他最多就100000个数,我开10005大小的数组。(刚开始脑抽,数组类型写成ListNode了/笑)

/**
 * 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) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        int arr[100005] = {};
        int i = 0;
        while(head != nullptr) {
            arr[i++] = head->val;
            head = head->next;
        }
        for(int j = 0, q = i-1; j < i/2; j++,q--) {
            if(arr[j] != arr[q]) return false;
        }
        return true;
    }
};

优雅vector,这样空间复杂度小了。

/**
 * 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) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        vector<int> vals;
        while(head != nullptr) {
            vals.emplace_back(head->val);
            head = head->next;
        }
        for(int i = 0, j = (int)vals.size() - 1; i < j; ++i, --j) {
            if(vals[i] != vals[j]) {
                return false;
            }
        }
        return true;
    }
};

插播一下std中的emplace_back函数:

 

相交链表

哈希集合

没想到这么做是因为对stl太不熟悉了,我最初只想到数组存储值,但后来一看,节点的值可以重复取,遂放弃。好伟大的哈希集合啊!

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode *> visited;
        ListNode *temp = headA;
        while(temp != nullptr) {
            visited.insert(temp);
            temp = temp->next;
        }
        temp = headB;
        while(temp != nullptr) {
            if(visited.count(temp)) {
                return temp;
            }
            temp = temp->next;
        }
        return nullptr;
    }
};

时间复杂度:Om+n,m为链表A的长度,n为链表B的长度

空间复杂度:Om

双指针

这个太妙了只能说。思路就看看吧先。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *pA = headA, *pB = headB;
        if(pA == nullptr || pB == nullptr) {
            return nullptr;
        }
        while(pA != pB) {
            if(pA == nullptr) {
                pA = headB;
            }
            else {
                pA = pA->next;
            }
            if(pB == nullptr) {
                pB = headA;
            }
            else {
                pB = pB->next;
            }
        }
        return pA;
    }
};

移除无序链表的重复节点

哈希集合

感觉哈希集合其实是很好用的。(上面那个删除有序链表中的节点,我感觉也可以用这个方法)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeDuplicateNodes(ListNode* head) {
        if(head == nullptr) return nullptr;

        unordered_set<int> visited;
        //你要说删除节点,我可要创建dum了哦
        //但其实这里不会删除头节点
        ListNode* dum = new ListNode(0);
        dum->next = head;
        ListNode* temp = dum;
        while(temp->next != nullptr) {
            if(visited.count(temp->next->val)) {
                temp->next = temp->next->next;
            } else {
                visited.insert(temp->next->val);
                temp = temp->next;
            }
        }
        return head;
    }
};

(不释放内存了我就展示算法)

时间复杂度:On,n为链表的节点个数

空间复杂度:On,哈希集合的长度(最坏情况n个节点的值都不同)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1555081.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

接口自动化框架搭建(八):pytest+allure+jenkins接入

1&#xff0c;安装allure插件 2&#xff0c;创建jenkins项目 怎么确定路径&#xff0c;可以查看工作空间&#xff0c;jenkins默认根目录就是工作空间 配置执行用例的命令&#xff0c;可以现在pycharm上试一下&#xff0c;然后在jenkins中配置&#xff1a; 把启动java服务的代…

Redis 和 Mysql 数据库数据如何保持一致性

一、操作 我们在实际项目中经常会使用到Redis缓存用来缓解数据库压力&#xff0c;但是当更新数据库时&#xff0c;如何保证缓存及数据库一致性&#xff0c;一般我们采用延时双删策略。 目前系统中常用的做法是一个查询接口&#xff0c;先查询Redis&#xff0c;如果不存在则查…

RockChip Android8.1 Settings

一:Settings一级菜单 1、AndroidManifest.xml 每个APP对应都有一个AndroidManifest.xml,从该文件入手分析最为合适。 packages/apps/Settings/AndroidManifest.xml 根据<category android:name="android.intent.category.LAUNCHER" />可找到当前当前APP a…

教程分享 | GitHub Copilot+ESP开发实战-SPI

准备工作 1. ESP32-C3-DevKitC-1 开发板一块 &#xff1b; 2.SPI转uart小板一块&#xff1b; 3.杜邦线4根、USB线 ; 教程分享 | GitHub CopilotESP开发实战-SPI 一、向Copilot提问 需要实现的功能&#xff1a; 1、ESP32-C3用C语言实现SPI初始化&#xff1b; 2、MOSI为7&a…

帆软报表踩坑日记

最近公司项目要是使用报表&#xff0c;公司使用的是帆软这个国产软件&#xff0c;自己也是学习使用&#xff0c;在使用的过程中记一下问题以及解决方式 公司使用的是帆软8这个版本&#xff0c;比较老了。 首先是表格中的扩展&#xff0c;就是当我们根据数据库查询数据然后放到表…

实时渲染是什么意思?实时渲染和离线渲染的区别

一、实时渲染是什么意思&#xff1f; 实时渲染是指在计算机程序运行时即时地生成图像和动画的过程&#xff0c;这种渲染技术通常用于网络游戏、虚拟现实和增强现实等需要实时交互的XR应用中。实时渲染需要在每秒内渲染数百万到数十亿个像素&#xff0c;以呈现出平滑的动画和交…

以XX医院为例的医疗建筑能效管理系统【建筑能耗 供电可靠 】

一、行业背景 二、行业特点 1.供电可靠性要求高&#xff1a;医院配电系统复杂&#xff0c;门诊、急救、手术室、ICU/CCU、血液透析等场合特一级和一级负荷比较多&#xff0c;一旦发生故障会造成严重影响&#xff0c;对配电可靠性要求极高。 2.能耗水平高&#xff1a;医院能耗…

vue3+threejs新手从零开发卡牌游戏(二十):添加卡牌被破坏进入墓地逻辑

在game目录下新建graveyard文件夹存放墓地相关代码&#xff1a; game/graveyard/p1.vue&#xff0c;这里主要设置了墓地group的位置&#xff1a; <template><div></div> </template><script setup lang"ts"> import { reactive, ref,…

UE5数字孪生系列笔记(三)

C创建Pawn类玩家 创建一个GameMode蓝图用来加载我们自定义的游戏Mode新建一个Pawn的C&#xff0c;MyCharacter类作为玩家&#xff0c;新建一个相机组件与相机臂组件&#xff0c;box组件作为根组件 // Fill out your copyright notice in the Description page of Project Set…

2.java openCV4.x 入门-hello OpenCV

专栏简介 &#x1f492;个人主页 &#x1f4f0;专栏目录 点击上方查看更多内容 &#x1f4d6;心灵鸡汤&#x1f4d6;我们唯一拥有的就是今天&#xff0c;唯一能把握的也是今天 &#x1f9ed;文章导航&#x1f9ed; ⬆️ 1.环境搭建 ⬇️ 3.Mat之构造函数与数据类型 hell…

关于深度学习的 PyTorch 项目如何上手分析?从什么地方切入?

文章目录 PyTorch 项目分析1.背景2.分析流程 PyTorch 项目分析 1.背景 当我们拿到一个 PyTorch 的深度学习项目时&#xff0c;应该怎么入手&#xff1f;怎么去查看代码&#xff1f; 2.分析流程 首先阅读对应项目的 README.md 文件。通过阅读 README.md &#xff0c;一般可以…

HarmonyOS 应用开发之创建PageAbility

开发者需要重写app.js/app.ets中的生命周期回调函数&#xff0c;开发者通过DevEco Studio开发平台创建PageAbility时&#xff0c;DevEco Studio会在app.js/app.ets中默认生成onCreate()和onDestroy()方法&#xff0c;其他方法需要开发者自行实现。接口说明参见前述章节&#xf…

UVA1388 - Graveyard (数学)

Graveyard 题面翻译 题目描述 在一个周长为 10000 10000 10000的圆上等距分别着 n n n个雕塑。现在又有 m m m个新雕塑加入(位置可以随意),希望所有 n m nm nm个雕塑在圆周上分布均匀。这就需要移动其中一些原有的雕塑。要求 n n n个雕塑移动的总距离尽量小。 输入格式 输…

前端-css-01

1.CSS 长度单位和颜色设置 1.1CSS 中的长度单位 px 像素 em 字体大小的倍数&#xff08;字体默认是16px&#xff09; % 百分比 1.2CSS 中的颜色设置方式 1.2.1使用颜色名表示颜色 red、orange、yellow、green、cyan、blue、purple、pink、deeppink、skyblue、greenyellow .…

国产暴雨AI服务器X3418开启多元自主可控新篇章

在当前数字化转型的大潮中&#xff0c;算力作为新质生产力的重要动力引擎&#xff0c;对推动经济社会发展起着关键作用。尤其在人工智能领域&#xff0c;随着高性能、安全可控的AI算力需求持续攀升&#xff0c;国产化服务器的研发与应用显得尤为迫切。 作为国内专业的算力基础…

aeon,一个好用的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个好用的 Python 库 - aeon Github地址&#xff1a;https://github.com/aeon-toolkit/aeon 在现代计算机科学和人工智能领域&#xff0c;处理时间序列数据是一个重要而复杂的任务。Python aeon库…

【Roadmap to learn LLM】Large Language Models in Five Formulas

by Alexander Rush Our hope: reasoning about LLMs Our Issue 文章目录 Perpexity(Generation)Attention(Memory)GEMM(Efficiency)用矩阵乘法说明GPU的工作原理 Chinchilla(Scaling)RASP(Reasoning)结论参考资料 the five formulas perpexity —— generationattention —— m…

mysql 常见运算符

学习了mysql数据类型&#xff0c;接下来学习mysql常见运算符。 2&#xff0c;常见运算符介绍 运算符连接表达式中各个操作数&#xff0c;其作用是用来指明对操作数所进行的运算。运用运算符 可以更加灵活地使用表中的数据&#xff0c;常见的运算符类型有&#xff1a;算…

PN8034芯朋微PN8034SSC-R1B非隔离SOP7封装12V300MA电源芯片

PN8034集成PFM控制器及650V高雪州能力智能功本MOSFET&#xff0c;用于外图元器件极精简的小功本非隔离开关电源。PN8034内置高压启动模块&#xff0c;实现系统快速启动&#xff0c;超低待机功能。该芯片提供了完整的智能化保护功能&#xff0c;包括过流保护&#xff0c;欠压保护…

QT_day3:信号和槽的连接方式

1、使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是…