当前位置: 首页 > news >正文

系统学习算法:专题十四 链表

前提知识:

1.画图,数据结构相关的题,画图必不可少,只要能画出来,那么后面的代码就很容易能写出来,因为将抽象的数据结构转换为直观的图画

2.引入虚拟头结点,也叫哨兵位,能够避免考虑很多边界情况

3.不要吝啬空间,如果你看到第二点的时候,如果反应是有些题也可以不用虚拟节点就能解决时,其实这就已经是有点吝啬空间的思想了,一个虚拟头结点才几个字节,根本不需要考虑,大胆创建就行了

4.快慢双指针,链表中经常且好用的方法

5.链表常用存在,创建节点,头插,尾插,其中头插是比较重要的,因为通过头插可以直接完成逆序链表的操作,而很多题目中都需要逆序链表,所以头插的操作要掌握,同时又结合虚拟头结点,那么头插起来会非常方便

题目一:

算法原理:

题意很简单,就是模拟加法的操作,只不过链表是逆序的,但是不要看是逆序的就觉得有难度,如果是正序的反而要更难一点,因为加法是从低位开始计算的,而逆序链表刚好就把低位放在了头结点,反而更好操作,而正序链表如果要进行加法,反而要先逆序再操作

思路就是用两个指针指向两个链表的头结点,然后开始往后遍历,将两数的和相加并记录在一个变量t里面,最后该位只取t的个位,然后/=10即可,而循环遍历结束条件是两个指针都为空时,且t也为0才结束,其中为什么t也要为0是解决下面这种情况

 即虽然两个指针为空,但还有一个进位没有进

也是可以用虚拟头结点,直接进行加法操作后,接在虚拟头结点之后就行

代码:

class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {//虚拟头结点ListNode newHead=new ListNode(0);//双指针ListNode cur1=l1,cur2=l2,cur=newHead;//记录进位int t=0;//循环遍历while(cur1!=null||cur2!=null||t!=0){//如果链表1还有值if(cur1!=null){t+=cur1.val;cur1=cur1.next;}//如果链表2还有值if(cur2!=null){t+=cur2.val;cur2=cur2.next;}//进位操作cur.next=new ListNode(t%10);cur=cur.next;t/=10;}//返回真正的头结点return newHead.next;}
}

题目二:

算法原理:

题意很简单,注意不能修改原结点的值,只能通过移动结点进行修改

如果还是用之前刚学链表时的思想,那就是不创建虚拟结点,同时也吝啬空间不定义变量,还不画图的话,那就脑袋里面慢慢绕了,一不小心就绕混了

因为如果不创建虚拟结点的话,12这两个结点的操作和34之间的操作是不一样的,34这里需要1这个结点指向4,而12这里没有前面的结点,因此需要自己找头结点,导致12的时候要特殊处理,不能进入循环,而创建虚拟头结点就可以让12操作和后面一样

如果创建虚拟结点后并画图,但吝啬空间的话,那么结点交换和修改就得考虑顺序且非常乱

class Solution {public ListNode swapPairs(ListNode head) {if(head==null){return head;}ListNode newHead=new ListNode(0,head);ListNode prev=newHead,cur=head;while(cur!=null&&cur.next!=null){prev.next=cur.next;prev=cur;ListNode tmp=cur.next.next;cur.next.next=cur;cur.next=tmp;cur=tmp;}return newHead.next;}
}

 这循环里面的代码顺序不能乱调,且一眼看过去也很乱

而直接定义变量的话就会这样

那么逻辑就直接是

prev指向next

next指向cur

cur指向nnext

且先后顺序随便,根本不影响

然后再修改变量,这里需要注意顺序,不然会出现覆盖

非常清晰

然后讨论循环终止条件

偶数结点情况下:

可以发现,如果cur==null就终止

奇数结点情况下:

 可以发现,如果next==null就终止

代码:

class Solution {public ListNode swapPairs(ListNode head) {//特殊处理空结点和单结点if(head==null||head.next==null){return head;}ListNode newHead=new ListNode(0,head);ListNode prev=newHead,cur=head,next=cur.next,nnext=next.next;while(cur!=null&&next!=null){//修改结点指向prev.next=next;next.next=cur;cur.next=nnext;//修改变量(注意顺序)prev=cur;cur=nnext;if(cur!=null){next=cur.next;}if(next!=null){nnext=next.next;}}return newHead.next;}
}

题目三:

算法原理:

这道题比较综合,通过模拟可以发现,无非是将前一半链表和后一半经过逆序的链表,进行合并即可,因此就涉及到三个知识点,找链表中间结点,逆序操作,合并链表

找链表中间结点,采用快慢双指针来解决

逆序链表,采用头插来解决

合并链表,模拟操作即可

需要注意的是,找到中间结点后,要将前后两个链表之间进行切断,实现两个独立的链表,才好方便进行后面的操作

代码:

class Solution {public void reorderList(ListNode head) {if(head.next==null){return;}//找到中间结点ListNode slow=head;ListNode fast=head;while(fast!=null&&fast.next!=null){slow=slow.next;fast=fast.next.next;}//拆开链表ListNode cur=head;while(cur.next!=slow){cur=cur.next;}cur.next=null;cur=head;//逆序链表ListNode newHead=new ListNode();while(slow!=null){ListNode slowNext=slow.next;slow.next=newHead.next;newHead.next=slow;slow=slowNext;}//合并链表newHead=newHead.next;while(cur!=null&&newHead!=null){ListNode curNext=cur.next;ListNode newHeadNext=newHead.next;cur.next=newHead;if(curNext!=null){newHead.next=curNext;  }   cur=curNext;newHead=newHeadNext;}}
}

题目四:

 算法原理:

题意很简单,就是按照升序合并k个链表,而且还比较好心的帮我们把k个链表进行了升序操作

我们之前学过合并2个链表,所以最容易想到的就是暴力解法,即遍历数组,第1个链表和第2个链表合并之后,再和第3个链表合并……

时间复杂度上,假设有k个链表,每个链表长度为n

合并的时间复杂度是n(k-1)+n(k-2)……+n=O(nk^2)=O(N^3)

效率非常低,所以要换一个方法

方法一:

既然是比较大小进行排序,那么就可以借助优先级队列来实现,将每个链表的头结点都扔进去,取出堆顶元素,就是所有头结点中最小的,然后对应的那个链表就扔下一个结点进去,然后再取堆顶元素,循环往复,直到对应链表为空,则停止添加,最后当堆为空时,则合并完成

时间复杂度上,堆的操作为log级别,有k个链表,所以要比较k次,又总共有n个节点,所以时间复杂度为O(n k logk)

代码一(优先级队列):

class Solution {public ListNode mergeKLists(ListNode[] lists) {//如果数组为空或者数组中的链表为空if(lists==null||lists.length==0){return null;}//创建一个小根堆PriorityQueue<ListNode> priorityQueue=new PriorityQueue<>((o1,o2)->o1.val-o2.val);//取出所有的头结点放进去for(ListNode node:lists){if(node!=null){priorityQueue.offer(node);}}//创建一个虚拟节点ListNode newHead=new ListNode();ListNode prev=newHead;//合并链表while(!priorityQueue.isEmpty()){ListNode cur=priorityQueue.poll();prev.next=cur;prev=cur;//如果该链表为空则不添加if(cur.next!=null){priorityQueue.offer(cur.next);}}//返回头结点return newHead.next;}
}

方法二:

既然合并k个链表可以拆分为n次合并2个链表,那么子问题是一样的,就可以采用分治递归的思想,以归并排序来解决,套模板即可

代码二(分治递归):

class Solution {public ListNode mergeKLists(ListNode[] lists) {//要求对lists数组从下标0开始到lists.length-1之间的链表进行合并并返回头结点return merge(lists,0,lists.length-1);}public ListNode merge(ListNode[] lists,int left,int right){//如果为空区间if(left>right){return null;}//如果只有一个链表,不用合并if(left==right){return lists[left];}//找到中间值,分为[left,mid],[mid+1,right]两个区间int mid=(left+right)/2;//递归处理左右两部分ListNode l1=merge(lists,left,mid);ListNode l2=merge(lists,mid+1,right);//合并两个链表ListNode newHead=new ListNode();ListNode prev=newHead;while(l1!=null&&l2!=null){if(l1.val>=l2.val){prev.next=l2;prev=l2;l2=l2.next;}else{prev.next=l1;prev=l1;l1=l1.next;}}if(l1!=null){prev.next=l1;}if(l2!=null){prev.next=l2;}//返回头结点return newHead.next;}
}

题目五:

算法原理:

题意很简单,就是不停翻转长度为k的链表,直到全部翻转完或者剩余长度不够k则停止

虽然是困难题,但是模拟的操作并不难

首先我们先遍历一遍链表,统计出链表的长度,然后再除k看看有多少组需要被翻转,假设为n组

然后循环n次,每次循环代表每一组,循环里面再嵌套循环k次,表示将k个结点进行翻转

最后拼接上后面未被翻转的结点

翻转也就是逆序,所以我们采用之前用的头插法

其中需要注意的是

每次头插翻转完一组后,后面结点跟的是第一次头插的结点后面

代码:

class Solution {public ListNode reverseKGroup(ListNode head, int k) {//如果翻转长度为1,则不用翻转if(k==1){return head;}//遍历链表统计长度ListNode cur=head;int len=0;while(cur!=null){cur=cur.next;len++;}//如果长度不够k,直接返回if(len<k){return head;}int n=len/k;//头插翻转cur=head;ListNode newHead=new ListNode(0);ListNode prev=newHead;ListNode tmp=null;//翻转n组for(int i=0;i<n;i++){//记录下一组的前驱结点tmp=cur; for(int j=0;j<k;j++){   ListNode next=cur.next;                  cur.next=prev.next;prev.next=cur;cur=next;}//更新前驱节点prev=tmp;}//拼接剩余节点prev.next=cur;//返回头结点return newHead.next;}
}
http://www.xdnf.cn/news/1175293.html

相关文章:

  • 华为7月23日机考真题
  • 关于在VS2022配置启动项目的问题
  • 表征工程中哪里用到内积 :内积vs余弦相似度--谁更胜一筹?
  • 力扣面试150题--搜索旋转排序数组
  • 开源 Arkts 鸿蒙应用 开发(九)通讯--tcp客户端
  • C#知识点表格大全
  • HDFS写性能优化技巧详解:从理论到实践
  • CSS 基础
  • 【科研绘图系列】R语言绘制黑白填充等显著性标记条形图
  • 网安-SQL注入-sqli-labs
  • 内积(Inner Product)和余弦相似度区别
  • LeetCode热题100--205
  • 糖尿病数据分析:血压与年龄关系可视化
  • 变频器带动电机:全方位解析参数变化
  • SparkSQL 聚合函数 MAX 对 NULL 值的处理
  • Linux -- 进程【下】
  • Python Day22 - 复习日
  • 如何用 Kafka + Redis + 线程池搭建高吞吐异步消息处理架构
  • 数据结构自学Day13 -- 快速排序--“前后指针法”
  • 西门子 S7-1500分布式 I/O通信 :PROFINET IO 与 PROFIBUS DP详解(下)
  • 电流、电压采集电路分析
  • 轻量化RTSP视频通路实践:采集即服务、播放即模块的工程解读
  • 【Windows命令手册】Windows中的常用命令,并与 Linux 做比较
  • Zookeeper学习专栏(七):集群监控与管理
  • FastGPT + Kymo:解锁企业专属知识库与智能体开发新体验
  • 【LeetCode 热题 100】78. 子集——(解法二)回溯+选哪个
  • Unity × RTMP × 头显设备:打造沉浸式工业远控视频系统的完整方案
  • AI营销核心技术解析:运作机制与行业应用实例
  • 炬森精密:缓冲滑轨的创新力量,重塑家居静音与安全新体验
  • 力扣MySQL(1)