【基础算法总结】链表

链表

  • 1.链表常用技巧和操作总结
  • 2.两数相加
  • 4.两两交换链表中的节点
  • 4.重排链表
  • 5.合并 K 个升序链表
  • 6.K 个一组翻转链表

在这里插入图片描述

点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃

1.链表常用技巧和操作总结

常用技巧

1.画图 !!! -> 直观 + 形象 + 便于我们理解

2.引入虚拟 “头” 节点

  1. 便于处理边界情况
  2. 方便我们对链表操作

3.不要吝啬空间,大胆去定义变量

比如都会遇到到这种题,前两句必须放前面,不然链表就断开了。但是我们可以定义一个next,这样就不用管按什么顺序了。
在这里插入图片描述

4.快慢指针

判环,找链表中环的入口,找链表中倒数第 n 个节点,都是用快慢指针解决的。

链表中的常用操作

1.创建一个新节点 new

2.尾插

3.头插

2.两数相加

题目链接:2. 两数相加

题目分析:

在这里插入图片描述

给两个链表,注意是逆序的。将两个数相加,还以逆序方式返回一个表示和的链表。

这道题给逆序正好方便我们从低位相加,如果是正序给的还要把链表逆置一下。
在这里插入图片描述
算法原理:

解法:模拟两数相加的过程即可

我们先来一个虚拟头结点,这样就少了判断为空的情况,直接尾插即可!在来一个 t 表示进位。t = cur1->val + cur2->val,每次都拿个数位构建节点。

在这里插入图片描述

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {

        ListNode* newhead, *tail;
        newhead = tail = new ListNode;//创建一个虚拟节点记录最终结果
        ListNode* cur1 = l1, *cur2 = l2;
        int t = 0; // 记录进位

        while(cur1 || cur2 || t) 
        {
            // 先加上第一个链表
            if(cur1)
            {
                t += cur1->val;
                cur1 = cur1->next;
            }
            // 加上第二个链表
            if(cur2)
            {
                t += cur2->val;
                cur2 = cur2->next;
            }

            tail->next = new ListNode(t % 10);
            tail = tail->next;

            t /= 10;
        }

        //防内存泄漏
        // tail = newhead->next;
        // delete newhead;
        // return tail;

        return newhead->next;

    }
};

4.两两交换链表中的节点

题目链接:24. 两两交换链表中的节点

题目分析:

在这里插入图片描述

两两交换链表的节点,注意不能直接交换里面的值,只能修改指针。这道题在递归、搜索回溯专题用递归的方法解决。这里用循环迭代的方式。

算法原理:

解法一:递归

解法二:循环、迭代(模拟)

引入一个头节点,这样就减少判断边界的问题。如果不引入,交换前两个节点和后面的节点写法是不一样的,因为还要返回头指针,所以就只能先处理前两个找到最终返回的头节点,然后在处理后面的。这样太麻烦了。引入头节点,因为已经有了头节点所有后面处理逻辑都是一样的。

因为我们要两两交换,这里我们需要四个指针。不要吝啬空间,大胆去定义变量 ,这样交换指针的时候,不用担心代码顺序导致找不到链表的问题,有了这四个指针随便先写那一步。交换之后指针都移动一下。

在这里插入图片描述

什么时候结束呢?节点可能有奇数个,也可能有偶数个。

可以看到当cur或者next为空的时候就结束了。
在这里插入图片描述

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {

        if(head == nullptr || head->next == nullptr) return head;

        ListNode* newhead = new ListNode;
        newhead->next = head;

        ListNode* prev = newhead, *cur = head, *next = head->next, *nnext = head->next->next;

        while(cur && next)
        {
            // 交换节点 
            prev->next = next;
            next->next = cur;
            cur->next = nnext;

            // 修改指针,注意nullptr指针解引用
            prev = cur;
            cur = nnext;
            if(cur) next = cur->next;
            if(next)nnext = next->next;
            
        }
        return newhead->next;

    }
};

4.重排链表

题目链接:143. 重排链表

题目分析:

在这里插入图片描述

给一个链表让按照规则重排一下。

算法原理:

解法:模拟

  1. 找到链表的中间节点
    快慢指针
  2. 把后面的部分逆序
    头插
  3. 合并两个链表
    (合并两个有序链表)双指针
    在这里插入图片描述

对于找到中间节点然后逆序,有两种做法。
在这里插入图片描述

第一种逆序策略:slow->next 后面逆序

因为这道题比较特殊可以将slow->next 后面逆序,因为你会发现逆序完之后中间位置还是在一起的。因此可以大胆将slow节点给第一个链表。

在这里插入图片描述

第二种逆序策略:从slow位置开始逆序

在这里插入图片描述

两种策略都是可以的。

在这里插入图片描述

但是如果使用头插法逆序,建议还是第一种策略,因为我们是想让两个链表断开的。如果想逆序后链表还是在一起的,就用第二种策略。

在这里插入图片描述
第一种策略

class Solution
{
public:
	 void reorderList(ListNode* head)
	{
		 // 处理边界情况
		 if(head == nullptr || head->next == nullptr || head->next->next == nullp
		 
		 // 1. 找到链表的中间节点 - 快慢双指针(⼀定要画图考虑 slow 的落点在哪⾥)
		 ListNode* slow = head, *fast = head;
		 while(fast && fast->next)
		 {
			 slow = slow->next;
			 fast = fast->next->next;
		 }
		 
		 // 2. 把 slow 后⾯的部分给逆序 - 头插法
		 ListNode* head2 = new ListNode(0);
		 ListNode* cur = slow->next;
		 slow->next = nullptr; // 注意把两个链表给断开
		 while(cur)
		 {
			 ListNode* next = cur->next;
			 cur->next = head2->next;
			 head2->next = cur;
			 cur = next;
		 }
		 // 3. 合并两个链表 - 双指针
		 ListNode* ret = new ListNode(0);
		 ListNode* prev = ret;
		 ListNode* cur1 = head, *cur2 = head2->next;
		 while(cur1)
		 {
			 // 先放第⼀个链表
			 prev->next = cur1;
			 cur1 = cur1->next;
			 prev = prev->next;
			 
			 // 再放第⼆个链表
			 if(cur2)
			 {
			 prev->next = cur2;
			 prev = prev->next;
			 cur2 = cur2->next;
			 }
		 }
		delete head2;
		delete ret;
	}
};

第二种策略

class Solution {
public:
    void reorderList(ListNode* head) {

        // 处理边界情况
        if(head == nullptr || head->next == nullptr || head->next->next == nullptr) return;

        // 1.找链表中间节点 -> 快慢指针(画图考虑slow的落点在哪里)
        ListNode* fast = head, *slow = head;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next->next;
        }

        // 2.将slow以及后面链表翻转 -> 头插法
        ListNode *cur = slow, *phead = nullptr, *next = nullptr;
        while(cur)
        {
            next = cur->next;
            cur->next = phead;
            phead = cur;
            cur = next;
        }

        // 3.合并两个链表 -> 双指针
        ListNode* newhead = nullptr, *tail = nullptr;
        newhead = tail = new ListNode(0);
        int i = 1;
        while(phead)
        {
            if(i%2 != 0)
            {
                tail->next = head;
                tail = head;
                head = head->next;
                
            }
            else
            {
                tail->next = phead;
                tail = phead;
                phead = phead->next;
            }
            ++i;
        }
        head = newhead->next;
        delete newhead;
    }
};

5.合并 K 个升序链表

题目链接: 23. 合并 K 个升序链表

题目分析:

在这里插入图片描述
前面学过合并两个有序链表,现在有k个有序链表让合并一下。

算法原理:

解法一:暴力求解
利用合并两个有序链表思想,可以先让前两个链表合并成一个新的链表,然后拿新的链表在和下一个链表合并。。。。直到把所有链表合并完。

在这里插入图片描述
但是时间复杂度很恐怖,假设每一个链表长度为n,共有k个链表。看合并几次有序链表。如果是第一个链表,需要合并k-1次,并且长度为n,所以第一个链表 时间复杂度 n(k-1)。第二个链表n(k-2)。。。所以最终时间复杂度为O(nk^2)

在这里插入图片描述

解法二:利用优先级队列做优化

合并K个有序链表,我们可以仿照合并两个有序链表的逻辑。先不考虑优先级队列,考虑如何对上面的做优化。

我们仿照合并两个有序链表的逻辑,先定义K个指针指向每一个链表,找出这个K个指针中值较小的节点,放在newhead的后面,放完之后,让这个指针往后移动。然后继续比较这K个指针指向的节点。这正好就是合并两个有序链表的逻辑。K个链表就K个指针,谁小谁就先连接newhead后面。

在这里插入图片描述

如何快速找到谁是K个节点中谁是较小的那个呢?
利用优先级队列。

因此我们的最终策略就是,搞一个小根堆,先将K个指针先丢到小根堆里,堆顶放的节点就是接下来我们要连接到newhead后面的节点。将堆顶节点连接到newhead后面之后,让这个指针往后移动然后进入优先级队列。此时堆顶也还是K个指针中最小的节点。。。。直到指针到空就不让这个链表进入队列了。等到所有链表的指针都到空了。说明链表合并结束了。

堆每次调整logk,一共进入nk个,所以这个时间复杂度O(nklogk)

class Solution 
{
public:
	//类中的仿函数不能支持我们将最小节点放在栈顶
	//因此指针并不是递增
	//所以自己定义一个仿函数用来支持将最小节点放在栈顶
    struct greater
    {
        bool operator()(const ListNode* x,const ListNode* y)
        {
            return x->val > y->val;
        }
    };

    ListNode* mergeKLists(vector<ListNode*>& lists)
    {
        if(lists.empty()) return nullptr;

        ListNode* newhead = new ListNode;
        ListNode* tail = newhead;
        priority_queue<ListNode*,vector<ListNode*>,greater> pq;

        for(int i = 0; i < lists.size(); ++i)
        {
            if(lists[i])
                pq.push(lists[i]);
        }


        while(!pq.empty())
        {
            // 出
            ListNode* cur = pq.top();
            tail->next = cur;
            tail = cur;
            pq.pop();

            //进
            if(cur->next)
                pq.push(cur->next);
        }
	
        return newhead->next;
    }
};

解法三:分治 - 递归
利用归并排序。

假设有6个链表,让把这6个合起来成一个有序链表。此时可以沿着中间将6个链表一分为二,左边三个链表,右边三个链表,现让左边三个合并成一个链表,然后在让右边三个合并成一个链表。然后拿着这个两个有序链表,在合并成一个有序链表。

两个有序链表,在合并成一个有序链表。我们是非常熟悉的。

在这里插入图片描述
现在重点就是上面的让左边三个合并成一个,右边三个合并成一个,应该怎么做呢?

其实是和这个大过程是一样的。以左边三个为例,策略和上面一样。把三个链表从中间分开。先左边一个合并成一个有序链表,在让右边两个合并成一个有序链表。然后在把这两个链表合并成一个有序链表。左右可以再分。逻辑是一模一样的,这整体就是一个递归过程!

在这里插入图片描述
此时我们就可以用递归来实现这个策略。并且和归并排序过程是一样的。

归并排序先分然后才合,时间复杂度我们紧盯每一个链表节点执行多少次。分就是一个完全二叉树。每一个链表都会合并,合并次数是这个数的高度次,假设有k个链表树高度logk,每一个链表都执行logk合并,一共有k个链表,每一个链表有n个节点,所以时间复杂度O(nklogk)

class Solution 
{
public:

 	ListNode* mergeKLists(vector<ListNode*>& lists)
    {

        return MergeSort(lists, 0, lists.size() - 1);
    }

    ListNode* MergeSort(vector<ListNode*>& lists, int left, int right)
    {
        if(left > right) return nullptr;

        if(left == right) return lists[left];

        int mid = left + (right - left) / 2;
        ListNode* newhead1 = MergeSort(lists, left, mid);
        ListNode* newhead2 = MergeSort(lists, mid + 1, right);

        ListNode* newhead = new ListNode;
        ListNode* tail = newhead;
        ListNode* cur1 = newhead1, *cur2 = newhead2;
        while(cur1 && cur2)
        {
           if(cur1->val < cur2->val)
           {
                tail->next = cur1;
                tail = cur1;
                cur1 = cur1->next;
           }
           else
           {
                tail->next = cur2;
                tail = cur2;
                cur2 = cur2->next;
           }
        }

        if(cur1)
            tail->next = cur1;

        if(cur2)
            tail->next = cur2;

        tail = newhead->next;
        delete newhead;
        return tail;
    }
};

6.K 个一组翻转链表

题目链接:25. K 个一组翻转链表

题目分析:

在这里插入图片描述
前面有一道题是两两一组翻转链表,现在是让k个一组翻转链表,小于k的就不用翻转了。
在这里插入图片描述

算法原理:

解法:模拟

  1. 先求出需要逆序多少组: n
  2. 重复 n 次,长度为 k 的链表的逆序即可(头插法)

先求出需要逆序多少组: n,剩下的就不逆序了,直接连接上就好了。申请一个头结点newhead,把k个节点头插到newhead后面即可。注意这只是第一组,下一组也要头插怎么办?因此我们需要一个tmp指针记录下一次执行头插的头结点在哪,prev在一次头插结束之后就更新一下 prev = tmp ,prev指向充当头结点。

在这里插入图片描述

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {

        // 1.先求出需要逆序多少组
        int n = 0;
        ListNode* cur = head;
        while(cur)
        {
            ++n;
            cur = cur->next;
        }
        n /= k;

        // 2.重复 n 次: 长度为 k 的链表逆序即可
        ListNode* newhead = new ListNode;
        ListNode* prev = newhead;
        cur = head;

        for(int i = 0; i < n; ++i)
        {
            ListNode* tmp = cur;
            for(int j = 0; j < k; ++j)
            {
                ListNode* next = cur->next;
                cur->next = prev->next;
                prev->next = cur;
                cur = next;
            }
            prev = tmp;
        }
        // 3.把不需要翻转的接上
        prev->next = cur;

        prev = newhead->next;
        delete newhead;
        return prev;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一个开源完全免费的无损视频或音频的剪切/裁剪/分割/截取和视频合并工具

大家好&#xff0c;今天给大家分享一款致力于成为顶尖跨平台FFmpeg图形用户界面应用的软件工具LosslessCut。 LosslessCut是一款致力于成为顶尖跨平台FFmpeg图形用户界面应用的软件工具&#xff0c;专为实现对视频、音频、字幕以及其他相关媒体资产的超高速无损编辑而精心打造。…

LabVIEW电子水泵性能测试平台

开发了一种车用电子水泵性能测试平台&#xff0c;该平台以工控机为载体&#xff0c;利用LabVIEW开发上位机软件&#xff0c;采用PLC控制阀门和水泵等电气元件&#xff0c;通过RS485进行数据采集并传输到上位机。通过上位机与下位机的协同控制&#xff0c;实现了数据交互处理和性…

备考美国数学竞赛AMC8和AMC10:吃透1850道真题和知识点

距离接下来的AMC8、AMC10美国数学竞赛还有几个月的时间&#xff0c;实践证明&#xff0c;做真题&#xff0c;吃透真题和背后的知识点是备考AMC8、AMC10有效的方法之一。 通过做真题&#xff0c;可以帮助孩子找到真实竞赛的感觉&#xff0c;而且更加贴近比赛的内容&#xff0c;…

c语言题目之喝汽水问题

文章目录 一、题目二、思路三、代码实现3.1方法一3.1方法二 一、题目 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 二、思路 20元首先可以喝20瓶&#xff0c;此时手中有20个空瓶子两个空瓶子可以喝一瓶&#xff0c;喝完之后&#xff0c;空瓶子剩余&…

跳水板00

题目链接 跳水板 题目描述 注意点 返回的长度需要从小到大排列必须正好使用k块木板0 < shorter < longer0 < k < 100000 解答思路 用k块两种不同的木板&#xff0c;组合数为k 1&#xff0c;最小的组合为全部使用shorter&#xff0c;每多一块longer&#xff0…

前端一面之 同步 vs 异步

异步 vs 同步 先看一下 下面的 demo console.log(100) setTimeout(function () {console.log(200) }, 1000) console.log(300) 执行结果 100 300 200console.log(100) alert(200) // 1秒钟之后点击确认 console.log(300) 这俩到底有何区别&#xff1f;—— 第一个示例中间的…

再度领跑的极兔速递不存在估值缺口?

随着618的尘埃落地&#xff0c;线上零售的最强辅助们也将陆续公布最新业务量数据了。 整体上&#xff0c;上半年国内快递业的需求量维持稳增势。据国家邮政局监测数据&#xff0c;截至6月30日&#xff0c;今年上半年我国快递业务量突破800亿件&#xff0c;比2023年提前59天。其…

echarts解决数据差异过大的问题

问题描述 使用echarts折线图和柱状图展示数据时&#xff0c;如果数据差异值较大&#xff0c;会导致显示图形差异过大&#xff0c;图表不美观。 如这一组数据[2000000, 200, 0.1, 20, 0, -10, -3000]&#xff0c;渲染出来的效果如下图&#xff1a; 可以看到由于最大值和最小值差…

netscaler LDAP+RADIUS传统的双因素认证方式(之一)

如果使用传统的双因素认证方式&#xff0c;可以通过在Citrix ADC (NetScaler) 13.1上配置Gateway Virtual Server来实现LDAP和RADIUS的双因素认证。当前配置方式&#xff0c;采用Cateway vServer两个Basic Authtication Policy方式实现&#xff0c;以下是详细步骤&#xff1a; …

【Python】使用PyQt6创建简单的登录界面

使用PyQt6创建简单的登录界面 介绍 PyQt6是Python绑定的Qt库&#xff0c;可以用来开发跨平台的桌面应用程序。本教程将介绍如何使用PyQt6创建一个简单的登录界面&#xff0c;包括用户名和密码输入框以及登录按钮。当用户点击登录按钮时&#xff0c;程序会验证输入的凭据并显示…

Matplotlib入门

#折线图用来表示数据的变化 plt.plot(x,y) #直方图用来统计连续性数据 无间隔 plt.hist(data数组,组数) #条形图用来统计离散的数组 并且反映其变化 有间隔 plt.bar(x,y,width 0.3) plt.barh(y,x,height 0.3) #散点图用来xy轴之间的联系 趋势 plt.scatter(x,y) #导入p…

Python大数据分析——K近邻模型(KNN)

Python大数据分析——K近邻模型 数学部分模型思想模型步骤距离度量指标欧氏距离曼哈顿距离余弦相似度 K值选择 代码部分函数示例1——知识掌握程度示例2——预测发电量 数学部分 模型思想 如图所示&#xff0c;模型的本质就是寻找k个最近样本&#xff0c;然后基于最近样本做“…

Qwen-7B推理教程【Python调用+web端部署】

前提 操作系统为Ubuntu22.04.4 LTS安装Anaconda&#xff08;本人安装教程如下&#xff09; Ubuntu22.04.4 LTS系统/安装Anaconda【GPU版】-CSDN博客 安装python3.9/pytorch/torchvision&#xff08;本人安装教程如下&#xff09; Ubuntu22.04.4系统/安装python3.9/pytorch/…

51单片机嵌入式开发:9、 STC89C52RC 操作LCD1602技巧

STC89C52RC 操作LCD1602技巧 1 代码工程2 LCD1602使用2.1 LCD1602字库2.2 巧妙使用sprintf2.3 光标显示2.4 写固定长度的字符2.5 所以引入固定长度写入方式&#xff1a; 3 LCD1602操作总结 1 代码工程 承接上文&#xff0c;在原有工程基础上&#xff0c;新建关于lcd1602的c和h…

前端项目本地的node_modules直接上传到服务器上无法直接使用(node-sasa模块报错)

跑 jekins任务的服务器不能连接外网下载依赖包&#xff0c;就将本地下载的 node_modules直接上传到服务器上&#xff0c;但是运行时node-sass模块报错了ERROR in Missing binding /root/component/node_modules/node-sass/vendor/linux-x64-48/binding.node >> 报错信息类…

深度学习设计模式之代理模式

文章目录 前言一、介绍二、详细分析1.核心组成2.实现步骤3.代码示例4.优缺点优点缺点 5.使用场景 总结 前言 代理模式是结构型设计模式&#xff0c;主要是为其他对象提供一种代理以控制对这个对象的访问。 一、介绍 代理模式&#xff08;Proxy Pattern&#xff09;是一种常用…

快速在springboot项目中应用EasyExcel

一、介绍 EasyExcel 是阿里巴巴开源的简化Excel文件读取和写入的开源库。主要的特点如下&#xff1a; 简单易用的API&#xff1a;EasyExcel提供简单API&#xff0c;隐藏处理Excel文件的底层细节。注解支持&#xff1a;支持使用注解将Java对象映射到Excel列&#xff0c;便于Ja…

《梦醒蝶飞:释放Excel函数与公式的力量》11.3 ISTEXT函数

第11章&#xff1a;信息函数 第三节 11.3 ISTEXT函数 11.3.1 简介 ISTEXT函数是Excel中的一个信息函数&#xff0c;用于检查指定单元格中的内容是否为文本。如果单元格内容是文本&#xff0c;则返回TRUE&#xff1b;否则返回FALSE。ISTEXT函数在数据验证、条件格式化和逻辑判…

【UE5.1】Chaos物理系统基础——06 子弹破坏石块

前言 在前面我们已经完成了场系统的制作&#xff08;【UE5.1】Chaos物理系统基础——02 场系统的应用_ue5&#xff09;以及子弹的制作&#xff08;【UE5.1 角色练习】16-枪械射击——瞄准&#xff09;&#xff0c;现在我们准备实现的效果是&#xff0c;角色发射子弹来破坏石柱。…

如何利用扩散实现结构功能动态调控?

如何利用扩散实现结构功能动态调控&#xff1f; 利用扩散机制在生物打印结构内部创建特定空间梯度的方法&#xff0c;主要分为两个方面&#xff1a; 1. 形成形态发生素梯度 形态发生素的作用&#xff1a;形态发生素是能够诱导细胞响应的信号分子&#xff0c;常用于生物打印结…