专题八_链表_算法专题详细总结

news2024/11/15 15:45:19

目录

链表

1.常用技巧

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

2)引入虚拟“头”节点

1.便于处理边界条件

2.方便我们对链表进行操作

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

4.快慢双指针

1.判断链表是否存在环

2.链表中的常用操作

1.创建一个新节点new

2.尾插

3.头插、

1. 两数相加(medium)

解析:

1)暴力:

2)优化:

总结:

2. 两两交换链表中的节点(medium)

解析:

1)暴力:

2)优化:

总结:

3. 重排链表(medium)

解析:

模拟:

第一步:eg:链表1->2->3->4->5

第二步:

第三步:

总结:

4. 合并 K 个升序链表(hard)

解析:

1)暴力:

2)优化:

1)大小堆:

2)分治_归并

步骤:

总结:

5. K个⼀组翻转链表(hard)

解析:

模拟:

那么这里就要补充一个狠狠狠重要的只是点了,三指针翻转链表:

总结:


链表

1.常用技巧

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

2)引入虚拟“头”节点

1.便于处理边界条件

2.方便我们对链表进行操作

要考虑很多的边界条件

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

4.快慢双指针

1.判断链表是否存在环

找到链表的入口

找到链表中倒数第n个节点

2.链表中的常用操作

1.创建一个新节点new
2.尾插

3.头插、

链表逆序,用头插贼方便,创建一个虚拟头节点,进行遍历就可以逆序。

那么就进入例题吧:

1. 两数相加(medium)

本题题意比较简单,意思就是两个已经被反转的链表只要直接相加即可,返回其相加后的链表,这里要注意的就是进位操作。

解析:

1)暴力:

我第一次做的时候,确实是个小白,什么都不会,将所有数字都存入字符串内,然后进行相加,也要考虑进位,或许将字符串转成int类型然后相加,在to_string()转成字符串也可以,但是这样太太太麻烦了。挺锻炼代码能力的,有兴趣可以试试。

2)优化:

在两个链表上设置移动指针cur1和cur2,然后设置进位next,在while里面判断cur1 和 cur2 是否为空,或者next是否存在进位,但凡有一个满足条件就能继续相加,添加到新链表的后端。这里注意的是,链表不一样长,那么就为了避免访问空指针的情况,就要先判断cur1==nullptr 和 cur2==nullptr,那么在这种情况下下,如果为空,就说明该链表已经遍历完,+0即可。

这里的进位需要注意的是先算出sum的总数,然后添加到链表的是sum%10 的余数,next表示进位,就让sum/10,证明存在进位的多少,让下一次链表相加就在加上这个进位。说到底熟能生巧,写多了,自然就会了。

/**
 * 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* head=new ListNode();
        ListNode* l=head;
        int next=0;
        while(l1||l2||next)
        {
            int sum=(l1?l1->val:0)+(l2?l2->val:0)+next;
            head->next=new ListNode(sum%10);
            next=sum/10;
            
            if(l1) l1=l1->next;
            if(l2) l2=l2->next;
            head=head->next;
        }
        return l->next;
    }
};

总结:

两个链表相加考虑进位,其实是挺简单的链表操作,可以直接在原链表上操作,一定要想清楚再写代码,这样负担很小,不要直接上手就写,但凡有一点思路不清楚就去画图!!!

2. 两两交换链表中的节点(medium)

题意很简单,两两交换链表间的节点,比如【1,2】【3,4】【5,6】等等,没有重复的节点,那么就要考虑的是,交换之后,怎么有链接回原来的链表。

解析:

1)暴力:

就是创建新的链表,然后分奇偶添加,但是这题不让创建新的链表。

2)优化:

这题实质就是模拟,再原链表上进行交换,那么只需要考虑的是原链表上交换的节点,又要连接到原链表上,就是要创建新的指针指向节点,这样交换后就不会存在节点丢失的情况。

那么本题就是两个节点间的交换,实质就是创建三个指针指向两个节点和交换后要链接的原链表上的节点。设置prev=head ,now=head->next,next=now->next;

这里专门设置了一个虚拟头节点,是为了能让反转的链表重新连接回来,其实这里也可以选择设置四个指针,再指向next后面的一个节点。这样就可以无脑交换连接,是不是很简单。这里就是要注意的是不要吝啬空间,想创建指针就创建,这样不会存在找不到原链表的情况。

两两交换完后,那么这三个指针就再次往后移动,让prev移动到被交换后的最后一个节点,这样能保证prev再次充当虚拟头节点。

/**
 * 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* swapPairs(ListNode* l) {
        ListNode* head=new ListNode();
        head->next=l;
        ListNode* prev=head;
        ListNode* now=head->next;
        ListNode* next=nullptr;
        if(now) next=now->next;

        ListNode* l1=head;
        
        while(now!=nullptr&&next!=nullptr)
        {
            now->next=next->next;
            prev->next=next;
            next->next=now;

            prev=now;
            now=prev->next;
            if(now)
            next=now->next;
        }
        return l1->next;
    }
};

总结:

关于在原链表内进行交换节点,那么就要考虑三个指针的遍历指向链表上的节点,然后进行无脑交换就OK,这样不用担心创建的新链表不会链接不上的问题。

3. 重排链表(medium)

题意比较简单就是模拟问题,重新排列这个链表

解析:

模拟:

第一步:eg:链表1->2->3->4->5

当都设指针prev和cur开始从第一个节点开始走,让cur先手走两步,prev走一步,达到快慢指针的目的,就能证明在cur走到链表尽头的时候,prev正好在链表的中间节点。

那么此时就将链表分开,形成两段链表。

eg:链表1->2->3->4->5

形成:h1->1->2;

           h2->3->4->5;

第二步:

翻转h2->3->4->5;

       h2->5->4->3;

我这里单独创建了一个翻转链表的函数,还是比较简单的,利用头插法,新界一个头节点,然后进行头插,自然就反转了链表。

第三步:

然后模拟h1 和 h2 交换链接的过程,h->1->5->2->4->3,即可。

模拟两个链表相互交换的过程,合并两个链表,不用吝啬空间,直接上去每个链表创建虚拟头节点,然后进行交换相连,然后再将三个指针往前移动即可。直到最后若存在没有被遍历完的节点直接连接到最后即可。

/**
 * 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:
    void reorderList(ListNode* l) {
        //将l分成两段链表
        //快慢双指针记录
        ListNode* cur=l,*prev=l;

        //prev停的位置就是链表的中点
        while(cur)
        {
            cur=cur->next;
            if(cur) cur=cur->next;
            prev=prev->next;
        }
        cur=l;
        while(cur->next!=prev)
            cur=cur->next;cur->next=nullptr;
 
        //创建两个链表 h1,h2;
        ListNode* h1=new ListNode(),*h2=new ListNode();
        h1->next=l;
        h2->next=prev;

        ListNode* t=h1->next;
        while(t)
        {
            cout<<t->val<<" ";
            t=t->next;
        }
        cout<<endl;


        ListNode* e=h2->next;
        while(e)
        {
            cout<<e->val<<" ";
            e=e->next;
        }      
        cout<<endl;

        //翻转链表(头插法)
        h2=turnlist(h2);  

         e=h2->next;
        while(e)
        {
            cout<<e->val<<" ";
            e=e->next;
        }      
        cout<<endl;
        

        //合并链表
        ListNode* r1=h1->next,*r2=nullptr; if(r1) r2=r1->next;
        ListNode* r3=h2->next,*r4=nullptr; if(r3) r4=r3->next;
        while(r1)
        {
            r1->next=r3;
            r1=r2;
            if(r2)
            r2=r2->next;
            if(r3)
            r3->next=r1;
            r3=r4;
            if(r4)
            r4=r4->next;
        }

    }

    ListNode* turnlist(ListNode* h)
    {
        ListNode* l1=h->next;
        ListNode* l=new ListNode();
        ListNode* l2=l;
        while(l1)
        {
            ListNode* newnode=new ListNode(l1->val);
            newnode->next=l->next;
            l->next=newnode;
            l1=l1->next;
        }
        return l2;

    }


};

总结:

这题确实很锻炼代码的模拟能力,十分推荐自己动手敲一遍。

4. 合并 K 个升序链表(hard)

题意很简单合并k个升序链表

解析:

1)暴力:

创建一个链表然后两两进行合并,每次两个链表都进行每个节点判断大小,小的添加到链表上,但是这样时间复杂度肯定很高,并且很麻烦,不过十分锻炼代码能力跟上题一样,有时间可以试试。

2)优化:

1)大小堆:

其实能想到用堆,优先级队列,这题是真简单,直接ac,属于秒杀题了,leetcode真是分不清,有些中等题难的要死。

这题如果用优先级队列,priority_queue(head->val),这里是大根堆,如果不进行翻转priority_queue(head->val,vector<int>,greater<int>) 小根堆的话,那么大根堆就进行头插,这样就可以保证完全有序,然后再插入新的链表,完成一个完整的新链表的创建。

总结:这里我之前用过set,虽然能排序,但会去重,我就又去multiset不会去重,但是每次删除的时候都会把所有相同的元素全都删除,那么唯一满足条件的就是优先级队列了priority_queue。

/**
 * 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* mergeKLists(vector<ListNode*>& lists) {
        priority_queue<int> q;
        int n=lists.size();
        for(int i=0;i<n;i++)
        {
            ListNode* cur=lists[i];
            while(cur)
            {
                q.push(cur->val);
                cur=cur->next;
            }
        }

        ListNode* head=new ListNode();
        while(!q.empty())
        {
            ListNode* newnode=new ListNode(q.top());
            q.pop();
            newnode->next=head->next;
            head->next=newnode;
        }
        return head->next;
    }
};
2)分治_归并

这种思想就是让数组一直细分,一直细分到最后,然后向上进行归并,根上一个专题介绍的一模一样,代码模板简直都是一模一样的,一定要取尝试一下。

步骤:

1.传送区间范围,计算中间值

2.递归左右两边

3.一左一右,合并两个链表

4.进行合并

/**
 * 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* mergeKLists(vector<ListNode*>& lists) {
        return mergeSort(lists,0,lists.size()-1);
    }
    ListNode* mergeSort(vector<ListNode*>& lists,int left,int right)
    {
        if(left>right) return 0;
        if(left==right) return lists[left];

        //计算中间值
        int mid=(left+right)>>1;

        //数组分两块【left,mid】 【mid+1,right】
        ListNode* l1=mergeSort(lists,left,mid);
        ListNode* l2=mergeSort(lists,mid+1,right);

        //合并两个链表
        return mergeTowlist(l1,l2);
    }

    ListNode* mergeTowlist(ListNode* l1,ListNode* l2)
    {
        ListNode* head=new ListNode();
        ListNode* cur1=l1,*cur2=l2,*prev=head;

        while(cur1&&cur2)
        {
            if(cur1->val<=cur2->val)
            {
                prev=prev->next=cur1;
                cur1=cur1->next;
            }
            else 
            {
                prev=prev->next=cur2;
                cur2=cur2->next;
            }
        }

        if(cur1) prev->next=cur1;
        if(cur2) prev->next=cur2;

        return head->next;
    }
    
};

总结:

本题有很多种办法,这里两种优化的方法都推荐取试试,真的写的很爽。

5. K个⼀组翻转链表(hard)

题意很简单理解,就是不停的翻转k个节点

解析:

模拟:

其实这就是简单的模拟题,思想挺简单的,有点暴力内味hhh~

就是开头遍历节点个数,然后计算count得出要经过多少次循环,每次循环都进行k个节点的翻转就ok,真的挺简单的,那么主要就是进行链表的翻转,然后再重新接回来。

开始我想到挺简单的,创建新的链表,然后进行链接,就是采用头插法,本来挺成功的,就这里卡了我一个小时,各种调试都没过,最后还是去看题解了,才发现用头插法连接到原链表上居然失效了,只能再原链表上直接进行翻转。

那么这里就要补充一个狠狠狠重要的只是点了,三指针翻转链表:

三指针原链表反转:
步骤:
1.node=prev->next; 用node来记录当前节点的下一个节点,这样在反转后能够找到下一个节点
2.prev->next=head ; 让当前节点指向head,head就相当于一个标记节点 表示反转链表的头部
3.head=prev; 每次反转成功后,head都往后移动到prev的位置,重新表示反转链表的头部
4.prev->next=node; 然后进入下一轮反转,要prev再次表示当前节点,就重新指向node
 

那么每次传入区间内的链表的节点,只要实现这三指针,就可以完美的完成链表的原地翻转,妈妈再也不用担心浪费空间了~

/**
 * 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* chang(ListNode* prev, ListNode* cur)
{
	ListNode* node=nullptr,*head=nullptr;
    while(prev!=cur)
    {
        node=prev->next;
        prev->next=head;
        head=prev;
        prev=node;
    }
    return head;
}


ListNode* reverseKGroup(ListNode* l, int k) {
	ListNode* head = new ListNode();
	head->next = l;
	ListNode* cur = head->next, * prev = head;
	int n = 0;
	while (prev->next) prev = prev->next, n++;
	int count = n / k;
	prev = head;
		
	while (count--)
	{
		ListNode* next = prev->next;
		for (int e = 0; e < k && cur != nullptr; e++) cur = cur->next;
		prev->next = chang(prev->next, cur);
		next->next = cur;
		prev = next;
	}



	return head->next;
}
};

总结:

这里最主要的就是关于在原链表上的直接翻转。哪怕用整整半天吃透我都觉得是值得的~

总结一下吧~这次的链表专题我学到了很多,希望也能帮到你!!!

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

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

相关文章

redis学习(014 实战:黑马点评:优惠券秒杀——1人只可以下1单问题解决方案)

黑马程序员Redis入门到实战教程&#xff0c;深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目 总时长 42:48:00 共175P 此文章包含第54p-第p55的内容 文章目录 一人一单问题分析第一种写法 查询后进行添加第二种写法 加悲观锁在用户上加悲观锁&#xff08;提…

Vue 响应式监听 Watch 最佳实践

一. 前言 上一篇文章我们学习了 watch 的基础知识&#xff0c;了解了它的基本使用方法及注意事项&#xff0c;本篇文章我们继续了解在Vue 中 响应式监听 watch 的妙用。了解 watch 的基础使用请参考上一篇文章&#xff1a; 详解 Vue 中 Watch 的使用方法及注意事项https://bl…

53 语言模型(和之后用来训练语言模型的数据集)_by《李沐:动手学深度学习v2》pytorch版

系列文章目录 文章目录 系列文章目录理论部分使用计数来建模N元语法总结 代码读取长序列数据随机采样顺序分区 小结练习 理论部分 在上一部分中&#xff0c;我们了解了如何将文本数据映射为词元&#xff0c;以及将这些词元可以视为一系列离散的观测&#xff0c;例如单词或字符…

(务必收藏)推荐市面上8款AI自动写文献综述的网站

在当前的学术研究和论文写作中&#xff0c;AI技术的应用已经变得越来越普遍。特别是在文献综述这一环节&#xff0c;AI工具能够显著提高效率并减少人工劳动。以下是市面上8款推荐的AI自动写文献综述的网站&#xff1a; 一、千笔-AIPassPaper 是一款备受好评的AI论文写作平台&…

java 框架组件

Java 框架是一系列预先编写好的、可复用的软件组件&#xff0c;它们旨在帮助开发者快速构建高质量的应用程序。Java 社区拥有众多优秀的框架&#xff0c;涵盖了从 Web 开发到大数据处理的各个领域。下面是一些流行的 Java 框架及其主要用途&#xff1a; Spring框架&#xff1a;…

基于丹摩智算部署SD3+ComfyUI文生图详解

目录 丹摩智算简介SD3ComfyUI文生图简介 SD3ComfyUI文生图部署步骤1.1、实例创建 操作步骤从HF-mirror下载SD3模型安装git安装ComfyUI 丹摩智算简介 丹摩智算官网&#xff1a;https://www.damodel.com/home 丹摩智算&#xff08;DAMODEL&#xff09;是一款专为AI应用打造的智…

网红挣钱太容易了

你看最近这个三只羊小Y哥&#xff0c;因为月饼质量问题、因为大闸蟹的问题&#xff0c;上了好多次热搜&#xff0c;掉粉了几百万。还是有很多人在赶着要买他们家的东西。 你是他的粉丝&#xff0c;他是你的屠夫。只要冠以“全网最低价”的名号&#xff0c;就会有无数的粉丝跑过…

应用层协议 --- HTTP

序言 在上一篇文章中&#xff0c;我们在应用层实现了一个非常简单的自定义协议&#xff0c;我们在我们报文的首部添加了报文的长度并且使用特定的符号分割。但是想做一个成熟&#xff0c;完善的协议是不简单的&#xff0c;今天我们就一起看看我们每天都会用到的 HTTP协议 。 UR…

华语童声璀璨新星陈千言、陈万语闪耀荣登2024年度最受媒体欢迎女歌手

华语童声璀璨新星陈千言、陈万语闪耀荣登2024年度最受媒体欢迎女歌手 近日&#xff0c;华语乐坛传来一则令人振奋的消息&#xff0c;11 岁的双胞胎姐妹花陈千言和陈万语荣获 2024 华语童声最受媒体欢迎女歌手和第 15 届华语金曲奖优秀童星两项大奖&#xff01; 陈千言和陈万语…

前缀和(2)_【模板】二维前缀和_模板

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 前缀和(2)_【模板】二维前缀和_模板 收录于专栏【经典算法练习】 本专栏旨在分享学习算法的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目…

centos磁盘逻辑卷LVM创建

centos磁盘逻辑卷LVM创建 一、磁盘逻辑卷LVM说明二、centos磁盘使用情况三、LVM安装指南1.LVM工具安装1. yum list lvm2. yum search lvm3. yum search pvcreate4. yum list lvm25. yum install lvm2 2.创建物理卷2.1磁盘情况查看2.2创建物理卷&#xff08;PV&#xff09; 3.创…

单词搜索问题(涉及递归等)

目录 一题目&#xff1a; 二思路解释&#xff1a; 三解答代码&#xff1a; 一题目&#xff1a; newcode题目链接&#xff1a; 单词搜索_牛客题霸_牛客网 二思路解释&#xff1a; 思路&#xff1a;个人理解是找到word中的第一个元素&#xff0c;然后去递归的上下左右查找&am…

python实现财会人工智能分享课件

前言&#xff1a; 一、财会与人工智能之间的联系 人工智能是计算机科学的一个分支&#xff0c;旨在模拟人类智能。自20世纪50年代起&#xff0c;AI经历了多个发展阶段&#xff0c;从规则基础系统到现在的深度学习技术&#xff0c;已经在医疗、制造、金融等多个行业得到广泛应用…

Volume数据管理

Volume 容器销毁时&#xff0c;保存在容器内部文件系统中的数据都会被清除&#xff0c;为了持久化保存容器的数据&#xff0c;可以使用kubernetes Volumevolume的生命周期独立于容器&#xff0c;Pod中的容器可能被销毁和重建&#xff0c;但Volume会被保留本质上&#xff0c;Kub…

【机器学习】Flux.jl 生态

官方API https://fluxml.ai/Flux.jl/stable/ecosystem/ 官网给出了 Flux’s model-zoo&#xff0c; 是一个庞大的案例库&#xff0c; 可以提供直观的参考&#xff0c; 并且还列举了基于 Flux.jl 开发的第三方库。 机器视觉 ObjectDetector.jl YOLO 抓取的“预备跑” 图像Met…

Vue3:作用域插槽

目录 一.性质 1.数据传递性 2.自定义显示方式 3.复用性 4.解耦性 二.作用 1.提高灵活性 2.增强可维护性 3.简化数据流 4.提升用户体验 三.使用 1.父组件 2.子组件 四.代码 1.父组件代码 2.子组件代码 五.效果 作用域插槽&#xff08;Scoped Slots&#xff09;…

一个很小的系统为什么负载那么高?

最近帮朋友优化一个系统&#xff0c;基本的情况如下&#xff1a;虚拟机&#xff0c;centos7.9&#xff0c;oracle 11.2.0.4&#xff0c;MES系统&#xff0c;数据量<50GB,日常session数不足100&#xff1b;按说这应该是一个负载很低的系统&#xff0c;但是用户却反映系统CPU经…

jQuery——jQuery的基本使用

1、使用 jQuery 核心函数&#xff1a;$ / jQuery 2、使用 jQuery 核心对象&#xff1a;执行 $&#xff08;&#xff09;返回的对象 3、引入 jQuery 函数库&#xff1a;可以本地引入&#xff08;不用联网&#xff09;&#xff0c;也可以远程引入&#xff08;需联网&#xff09…

GIS开发常用的开源地图数据框架有哪些?

学完Web前端开发&#xff0c;还需要掌握哪些内容&#xff1f;本篇文章再给大家主要讲讲针对WebGIS开发的地图和可视化数据库。 Echarts ECharts是一个使用 JavaScript 实现的开源可视化库。它可以流畅的运行在 PC 和移动设备上&#xff0c;兼容当前绝大部分浏览器&#xff08;…

OpenHarmony(鸿蒙南向)——平台驱动指南【DAC】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 概述 功能简介 DAC&#xff08;Digital to Analog Converter&…