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

【Java】分割链表、回文链表、相交链表、环形链表、环形链表II、反转链表、链表的中间节点、返回链表倒数第k个节点的值、合并两个有序链表

学习完单链表,接下来做一些练习题。

一、反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

(只遍历链表一遍,同时在链表本身上进行修改)

思路:实现链表反转,可以使用头插法来完成 —— 就是除了头节点head之外的所有节点,都一个个拿过来头插到head前面,最终原来的head变成尾节点,原来的尾节点变成新的头节点head,从而实现反转链表。

具体做法:

  1. 原来的head会变成尾节点,那么首先可以将head节点的next引用变成null,在变成null之前,先将head的next下一个节点保存起来,否则会因为找不到下一个节点的位置而无法进行头插,即cur=head,head.next=null
  2. 遍历链表,将第二个节点头插到head节点前,然后更新头节点head为第二个节点,接着继续往后走,再将第三个节点重复上述的动作(不过在每次头插之前,先保存下一个节点的位置,否则将第二个节点头插完成后,就无法找到第三个节点的位置),直到链表中的节点都头插完成为止,即cur=curNext(先保存下一个节点位置),cur.next=head(头插到头节点前面),head=cur(更新新的头节点位置),cur=curNext(继续往后走)。
  3. 如果链表为空的话,直接返回null;否则返回反转之后的链表。

public ListNode reverseList(ListNode head) {//判空if(head == null) {return null;}//将head的下一个节点保存起来ListNode cur = head.next;//head的next引用改为nullhead.next = null;//从第二个节点开始头插while(cur != null) {ListNode curNext = cur.next;//保存下一个节点的位置cur.next = head;//头插到head前head = cur;//更新头节点cur = curNext;//cur继续往后走}return head;//返回反转后的链表
}

二、链表的中间节点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

有三种情况:

  1. 如果链表中的节点是奇数个节点,那么就直接返回中间节点。
  2. 如果链表中的节点是偶数个节点,那么就返回中间两个节点的第二个节点,即第二个中间节点。
  3. 如果链表为空,直接返回null。

思路:快慢指针 —— 在这道题中,fast快指针一次走两步,slow慢指针一次走一步,当节点是奇数个时,保持fast一次两步,slow一次一步,同时往后走,当fast走到尾节点时(fast.next=null),刚好slow走到中间节点的位置,此时返回slow节点即可;当节点是偶数个时,保持fast一次两步,slow一次一步,同时往后走,当fast走到空时(fast=null),刚好slow走到第二个中间节点的位置,此时返回slow节点即可。

注意:fast=null 和 fast.next=null 这两个条件必须同时满足(fast=null && fast.next=null),而且&&两边的条件不能交换位置,如果位置颠倒,即 fast.next=null && fast=null ,当fast的next是null时,即使fast本身不是null,但是它的next是null,就进不去循环,将&&两边交换回去,就能避免这种情况。

public ListNode middleNode(ListNode head) {//判空if(head == null) {return null;}//定义fast、slow指针,初始时等于headListNode fast = head;ListNode slow = head;//保持fast一次两步,slow一次一步同时移动while(fast != null && fast.next != null) { //条件不能互换fast = fast.next.next;slow = slow.next;}return slow;//此时slow是中间节点
}

三、返回链表倒数第k个节点的值

输入一个单链表,找出单链表中倒数第k个节点,返回该节点的值(保证k必须有效)

思路:使用快慢指针 —— 返回倒数第 k 个节点,fast先单独走 k-1 步,然后fast和slow同时往后走,直到slow节点刚好是第 k 节点,此时返回slow节点的值。

具体做法:

创建一个count变量,用来计数fast单独走的次数,即count != k-1 (当k=3,那么fast走2步,即count != 2,fast走2步——0,1),然后让fast和slow同时往后走,由于每次当slow节点刚好走到第k节点时,fast都在尾节点处,无论链表中是奇数个节点还是偶数个节点,因此,fast和slow同时往后走的条件 —— fast.next != null

注意:还是要对链表进行判空,且对 k 的有效性进行判断 —— 如果 k <=0 或者 k > 链表长度 的情况,k是不合法的;那么k<=0的情况很好解决,k>链表长度的情况,在不求链表长度的情况下,该如何去表达呢?—— 在每次 fast 单独往后走后,判断一下此时的fast节点是否为null,如果是,则表示此时已经到达链表的最大长度,表示此时是 k>链表长度 的情况,k不合法;如果不是,则继续往后走。

详情如下图:

public int kthToList(ListNode head,int k) {//判空    if(head == null) {return -1;}//判断k的合法性/有效性if(k <= 0) {return -1;}ListNode fast = head;ListNode slow = head;int count = 0;//计数fast单独走的次数while(count != k-1) {fast = fast.next;//k > 链表长度 的情况if(fast == null) {return -1;}count++;}//fast和slow同时往后走while(fast.next != null) {fast = fast.next;slow = slow.next;}//此时slow就是倒数第k个节点return slow.val;//返回该节点的值
}

四、合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

思路:为新链表创建一个哨兵节点newHead,创建一个临时节点tmp保存newHead,newHead哨兵节点保持不变,当第一个链表list1中第一个节点的值小于第二个链表list2中第一个节点的值时,list1的第一个节点尾插到新链表中tmp(newHead)的next下一个节点的位置,然后tmp往后走,list1往后走;当第一个链表list1中第一个节点的值大于第二个链表list2中第一个节点的值时,list2的第一个节点尾插到新链表中tmp(newHead)的next下一个节点的位置,然后tmp往后走,list2往后走。

list1 和 list2 中,只要有一个链表中有节点走到null,那么就说明这一个节点已经没有节点了,而另一个中可能还有节点,那么就将还有节点的链表直接尾插到新链表中即可,最后返回newHead的next下一个节点,即第一个有效节点的位置。

(关于哨兵节点不懂的看前一篇文章)

public ListNode mergeTwoList(ListNode headA,ListNode headB) {//创建新链表ListNode newHead = new ListNode(-1);ListNode tmp = newHead;//遍历两个链表list1、list2while(headA != null && headB != null) {if(headA.val < headB.val) {tmp.next = headA;headA = headA.next;tmp = tmp.next;}else {tmp.next = headB;headB = headB.next;tmp = tmp.next;}}//此时只有其中一个链表中还有节点if(headA != null) {tmp.next = headA;}if(headB != null) {tmp.next = headB;}return newHead.next;//返回新链表的第一个有效节点
}

五、分割链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头节点。

思路:首先我们创建两个新链表,分别存放值小于x的节点和值大于x的节点,其中,用beforeStart表示值小于x节点的头节点的引用,beforeEnd表示尾节点的引用;用afterStart表示值大于x节点的头节点的引用,afterEnd表示尾节点的引用;由于两个链表中初始状态都没有节点,因此它们的初始值都是null。

接下来遍历链表,

  • 当cur节点的val值小于x时,那么就要插入到值小于x节点的链表中,而一开始时该链表是空的,因此此时的beforeStart和beforeEnd都指向cur,即beforeStart=beforeEnd=cur;接着遍历,如果此时的cur.val还是小于x,那么继续插入到该链表中,此时的链表不再是空,那么尾插到该链表中,然后更新插入的节点为新的尾节点,即beforeEnd.next=cur,beforeEnd=beforeEnd.next
  • 当cur节点的val值大于x时,那么就要插入到值大于x节点的链表中,而一开始时该链表是空的,因此此时的afterStart和afterEnd都指向cur,即afterStart=afterEnd=cur;接着遍历,如果此时的cur.val还是大于x,那么继续插入到该链表中,此时的链表不再是空,那么尾插到该链表中,然后更新插入的节点为新的尾节点,即afterEnd.next=cur,afterEnd=afterEnd.next。

完成所有的分类插入后,将值小于x节点的链表和值大于x节点的链表连接起来,即beforeEnd.next=afterStart,返回这两个链表的新的头节点beforeStart。

但是,还要注意对 beforeStart 进行判空:如果原链表中所有节点的val值都大于x,那么意味值小于x节点的链表为空,应该返回的是值大于x节点的链表的头节点 afterStart 。

而如果是原链表中所有节点的val值都小于x,那么直接返回 beforeStart ,如果是有小于和大于的情况,还需要注意对值大于x节点的链表的尾节点 afterEnd 的next节点进行判断,必须将其置为null,避免陷入死循环,即afterEnd.next=null,最后在返回 beforeStart 。

public ListNode pratition(ListNode head,int x) {//值小于x的节点的链表ListNode beforeStart = null;ListNode beforeEnd = null;//值大于x的节点的链表ListNode afterStart = null;ListNode afterEnd = null;//遍历原链表ListNode cur = head;while(cur != null) {if(cur.val < x) {if(beforeStart == null) { //第一次插入beforeStart = beforeEnd = cur;}else {beforeEnd.next = cur;beforeEnd = beforeEnd.next;}}else { //值大于x情况if(afterStart == null) { //第一次插入afterStart = afterEnd = cur;}else {afterEnd .next = cur;afterEnd = afterEnd .next;}}}//如果全部的节点中的值都是大于x的,则返回afterStartif(beforeStart == null) {return afterStart;}//有小于也有大于,将存放值小于x节点的链表和存放值大于x的链表连接起来beforeEnd.next = afterStart;//如果afterStart中有节点,那么将afterEnd的next节点置为null,防止陷入循环if(afterStart != null) {afterEnd.next = null;}return beforeStart;
}

六、回文链表

编写一个方法,检查输入的链表是否是回文的,给定一个链表的头节点head,请返回一个boolean值,代表其是否为回文结构。(不能申请额外的内存)

思路:判断一个链表是否为回文链表,可以从它的头节点和尾节点开始,向它们的中间节点比较,看每个节点的val值是否相同,但是,问题在于这个链表是一个单链表,只能单向遍历,那么如何使它的尾节点向中间节点和它的头节点向中间节点 之间的节点进行比较?

—— 使用快慢指针的思路:

  • 先找到中间节点的位置,然后对中间节点和尾节点之间的节点进行翻转 ,使其达到可以“双向遍历”的效果,具体做法:首先定义一个节点cur,它表示的是中间节点slow的next节点,从cur开始,遍历链表,将cur的next节点原本指向的是它的下一个节点,但是要实现翻转,于是将cur的next节点改成指向它的前一个节点,即中间节点slow,然后slow往后走,变成cur,即slow=cur(注意,在进行翻转之前,先保存一下cur原本的next节点 curNext),cur也继续往后走,即cur=curNext,接着继续修改新cur的next的指向,指向它的前一个节点slow,随后slow、cur往后走,直到cur节点为null位置,表示翻转成功。
  • 最后是进行回文判断:经过上面的操作,此时的slow已经走到了尾节点的位置,而head依然表示头节点的位置,head和slow不能相同,分别从head和slow节点位置开始,向中间节点比较,此时又分为2种情况:
  • 1.当链表节点是奇数个时,判断head.val 和 slow.val 是否相等,如果不等,则直接返回false,否则head和slow往后走,最终走到中间节点位置时 head==slow,循环停止,此时返回true。
  • 2.当链表节点是偶数个时,除了上述奇数个的情况外,还需要一个条件:我们知道,偶数节点的中间节点是第二个中间节点的位置,那这意味着当slow走到中间节点时,head此时刚好走到slow的前一个节点,两者的val值相同,继续往后走,此时head变成中间节点位置,而slow往后走之后变成了它后一个节点的位置,即中间节点后一个节点位置,这个节点之前已经比较了,这也说明翻转破坏了链表的结果,因此,需要加这个条件——如果 head.next=slow 的话,也就是slow是中间节点位置,head在前一个位置,如果这两个节点相等,则说明这个链表是回文结构,也说明这个链表所有节点已经比较完成,返回true。

注意:如果链表是一个空链表,它也表示是回文链表

public boolean isPalindrome() {if(head == null) {return true;//空链表也是一个回文链表}//1.找中间节点ListNode fast = head;ListNode slow = head;while(fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;}//此时的slow是中间节点//2.进行翻转ListNode cur = slow.next;//从slow的下一个节点cur开始翻转while(cur != null) {ListNode curNext = cur.next;//翻转前保存下一个节点位置cur.next = slow;//翻转slow = cur;//slow往后走cur = curNext;//cur往后走}//此时中间节点和尾节点之间的节点翻转完成//3.判断回文while(head != slow) {if(head.val != slow.val) {return false;}//判断偶数个节点的情况if(head.next == slow) {return true;}head = head.next;slow = slow.next;}//判断奇数个节点的情况return true;
}

七、相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null。

相交的两个链表,长这样:

示例:如下图,是两对相交链表,通过这两对链表,可以知道:在相交节点前,两个链表的节点数可以是不同的,但是在相交节点之后的包括相交节点,节点数是相同的。

左边一对链表,headA链表的长度是5,headB链表的长度是6;右边一对链表,headA链表的长度是6,headB链表的长度是6。

思路:两个链表的的长度有三种情况:headA链表比headB链表长、headA链表比headB链表短、headA链表和headB链表一样长;不管是上述的哪一种情况,默认先将headA设置为最长的那个链表,存放在longList链表中,headB设置为短的链表,存放在shortList链表中。

  1. 首先,需要做的是分别求两个链表的长度lenA、lenB,然后求这两个链表的差值,即len=lenA-lenB,如果len是大于0的情况,那么就是headA链表较长,上述的默认设置就不需要改;如果len是小于0的情况,那么就是headB链表较长,就需要将headB改放在longList长链表中,将headA改放在shortList短链表中,然后len变成lenB-lenA。
  2. 接着我们让长链表走两个链表的差值,也就是走len步,走完之后,如果两个链表存在相交的节点,此时的长链表和短链表距离相交的节点的距离相同,它们同时一起往后走,直到它们相遇,最终返回相交的节点。如果两个链表同时往后走时,一直没有遇到相交节点,即是长短链表都是是一条”直线“,表示它们没有相交,返回null。

public ListNode getIntersectionNode(ListNode headA,ListNode headB) {//默认长链表longList放的是headA,短链表shortList放的是headBListNode longList = headA;ListNode shortList = headB;//1.求两个链表各自的长度int lenA = 0;int lenB = 0;while(longList != null) {lenA++;longList = longList.next;}while(shortList != null) {lenB++;shortList = shortList.next;}longList = headA;shortList = headB;//2.求2个链表的差值int len = lenA - lenB;//如果是headB链表长的情况if(len < 0) {longList = headB;shortList = headA;len = headB - headA;}//走完上述的步骤,longList一定是最长的链表,shortList一定是最短的那个链表//3.让最长的链表走差值 即走len步while(len != 0) {longList = longList.next;len--;}//4.两个链表的引用同时往后走,直到它们相遇while(longList != shortList) {longList = longList.next;shortList = shortList.next;}//如果同时走完没有遇到相交的链表,则说明不相交if(longList == null) {return null;}return longList;//返回相交节点
}

八、环线链表

给你一个链表的头节点 head ,判断链表中是否有环。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。如果链表中存在环,则返回 true。 否则,返回false。 

思路:快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表 带环则一定会在环中相遇,否则快指针率先走到链表的末尾。

  • 为什么快指针每次走两步,慢指针走一步可以?

假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当 慢指针 刚进环时,可能就和 快指针 相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动一次,之间的距离 就缩小一步,不会出现每次刚好是套圈的情况,因此:在慢指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。

  • 慢指针一次走1步,而快指针一次走3步,走4步,...n步行吗?

假设 快指针每次走3步,慢指针每次走1步,此时快指针肯定先进环,慢指针后来才进环。此时慢指针进环的时候,快指针的位置如图所示:

此时按照上述方法来绕环移动,每次快指针走3步,慢指针走1步,是永远不会相遇的,快指针刚好将慢指针套圈了,因此不行。

只有快指针走2步,慢指针走1步才可以,因为环的最小长度是1,即使套圈了两个也在相同位置;不过像快指针走3步,慢指针走2步等方式也是可以的,只要它们相差的是一个环的长度就可以。

public boolean hasCycle() {ListNode fast = head;ListNode slow = head;while(fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;if(fast == slow) {  //如果链表中有环,那么fast和slow总会有相遇的那一次return true;}}return false;
}

九、环形链表II

给定一个链表的头节点,返回链表开始入环的第一个节点。如果链表无环,则返回null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。不允许修改链表。

思路:快慢指针 —— 根据上一道题的情况,当慢指针从入口点进入环时,快指针可能已经在环中绕了n圈了,n至少为1,因为快指针先进环走到相遇点的位置,最后又会在相遇点与慢指针相遇;而且,慢指针进环后,快指针肯定会在慢指针走完一圈之内追上慢指针,因为:慢指针进环后,快慢指针之间的距离最多就是环的长度,而两个指针在移动时,每次它们至今的距离都缩减一步,因此,在慢指针移动一圈之前快指针肯定可以追上慢指针的。

快指针速度是慢指针的2倍,因此,会有如下的关系:

结论:让一个指针从链表起始位置开始遍历链表,同时让一个指针从相遇点的位置开始绕环运行,两个指针 都是每次均走一步,最终肯定会在入口点的位置相遇

public ListNode detectCycle(ListNode head) {ListNode fast = head;ListNode slow = head;//找到环的相遇点while(fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;if(fast == slow) {break;//相遇,表示有环,跳出循环}} if(fast == null || fast.next == null) {return null;//没有环}slow = head;////slow从头节点开始,而fast在相遇点开始,slow和fast同时向入环点一步步走while(slow != head) {fast = fast.next;slow = slow.next;}return slow;//返回入环点
}
http://www.dtcms.com/a/480048.html

相关文章:

  • 公司网站公司简介宣传夸大受处罚网站建设知名
  • 企业做网站的公司有哪些网站cms在线识别
  • 重庆网站seo分析wordpress 底部修改插件
  • 制作手机广告的网站网站推广工作计划
  • 网站策划书背景介绍响应式布局页面
  • 微信小程序怎么做网站链接官方网站英语
  • 哈尔滨哪里有做网站的电子商务官网首页
  • 随身WiFi技术深探:通信芯片/信号增益原理解析+开源方案参考!随身WiFi建议买吗?随身WiFi品牌哪个网速快信号稳定?格行随身WiFi怎么样?
  • 上海市工程信息网站安阳哪里做360网站
  • 鲜花网站建设的项目介绍用网站源码做网站
  • 网站建设需要怎么做开封网站建设培训班
  • 织梦网站加网站地图深圳百度公司地址
  • sns网站设计南昌新力中心 nanchang sinic center
  • 大石桥做网站打开百度搜索网站
  • 石碣镇网站仿做怎么建商城网站吗
  • 汽车租赁网站设计登录我的博客
  • 淘宝网站建设教程视频教程营口网站开发
  • 建设银行徐州分行网站免费vps
  • 想做一个自己的网站 怎么做wordpress怎么播放视频播放器
  • 云南网站建设模块全网热搜榜
  • 建立本地 APT 仓库教程
  • 网站建设主要干什么网站经营网络备案信息管理系统
  • 黑龙江省建设部网站免费中文网页模板
  • 交互式网站备案分类信息网站建设系统
  • PostgreSQL REST API 介绍
  • 做网站刷流量挣钱吗大学生实训网站建设心得
  • 哪些网站可以做招商广告crm管理系统定制
  • 东莞网站seo公司平台网站开发简报
  • 云南建设工程网站发稿媒体平台
  • 网站设计需求表西北电力建设甘肃工程公司网站