左神算法:两个单链表相交的一系列问题(链表是否有环 / 两无环链表是否相交 / 两有环链表是否相交)
本题来自左神《程序员代码面试指南》“两个单链表相交的一系列问题”题目。
题目
在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 head1 和 head2,这两个链表可能相交,也可能不相交。请实现一个函数,如果两个链表相交,请返回相交的第一个节点;如果不相交,返回 null 即可。
要求:如果链表 1 的长度为 N,链表 2 的长度为 M,时间复杂度请达到 O(N+M),额外空间复杂度请达到 O(1)。
题解
本题可以拆分成 三个子问题,每个问题都可以作为一道独立的算法题,具体如下。
- 问题一:如何判断一个链表是否有环,如果有,则返回第一个进入环的节点,没有则返回null。
- 问题二:如何判断两个无环链表是否相交,相交则返回第一个相交节点,不相交则返回null。
- 问题三:如何判断两个有环链表是否相交,相交则返回第一个相交节点,不相交则返回null。
注意:如果一个链表有环,另外一个链表无环,它们是不可能相交的,直接返回null。下面逐一分析每个问题。
问题一:如何判断一个链表是否有环,如果有,则返回第一个进入环的节点,没有则返回 null。
如果一个链表没有环,那么遍历链表一定可以遇到链表的终点;如果链表有环,那么遍历链表就永远在环里转下去了。如何找到第一个入环节点,具体过程如下:
1.设置一个慢指针slow 和一个快指针fast。在开始时,slow 和fast 都指向链表的头节点head。然后slow 每次移动一步,fast 每次移动两步,在链表中遍历起来。
2.如果链表无环,那么fast 指针在移动过程中一定先遇到终点,一旦fast 到达终点,说明链表是没有环的,直接返回null,表示该链表无环,当然也没有第一个入环的节点。
3.如果链表有环,那么 fast 指针和 slow 指针一定会在环中的某个位置相遇,当fast 和slow 相遇时,fast 指针重新回到head 的位置,slow 指针不动。接下来,fast 指针从每次移动两步改为每次移动一步,slow 指针依然每次移动一步,然后继续遍历。
4.fast 指针和slow 指针一定会再次相遇,并且在第一个入环的节点处相遇。证明略。
注意:你也可以用哈希表完成问题一的判断,但是不符合题目关于空间复杂度的要求。
问题一的具体实现请参看如下代码中的 getLoopNode 方法。
public static Node getLoopNode(Node head) {if (head == null || head.next == null || head.next.next == null) {return null;}Node n1 = head.next; // n1 -> slowNode n2 = head.next.next; // n2 -> fastwhile (n1 != n2) {if (n2.next == null || n2.next.next == null) {return null;}n2 = n2.next.next;n1 = n1.next;}n2 = head; // n2 -> walk again from headwhile (n1 != n2) {n1 = n1.next;n2 = n2.next;}return n1;
}
如果解决了问题一,我们就知道了两个链表有环或者无环的情况。如果一个链表有环,另一个链表无环,那么这两个链表是无论如何也不可能相交的。
能相交的情况就分为两种,一种是两个链表都无环,即问题二;另一种是两个链表都有环,即问题三。
问题二:如何判断两个无环链表是否相交,相交则返回第一个相交节点,不相交则返回null。
如果两个无环链表相交,那么从相交节点开始,一直到两个链表终止的这一段,是两个链表共享的。解决问题二的具体过程如下:
1.链表1 从头节点开始,走到最后一个节点(不是结束),统计链表1 的长度记为len1,同时记录链表1 的最后一个节点记为end1。
2.链表2 从头节点开始,走到最后一个节点(不是结束),统计链表2 的长度记为len2,同时记录链表2 的最后一个节点记为end2。
3.如果end1!=end2,说明两个链表不相交,返回null 即可;如果end==end2,说明两个链表相交,进入步骤4 来找寻第一个相交节点。
4.如果链表1 比较长,链表1 就先走len1−len2 步;如果链表2 比较长,链表2 就先走len2−len1 步。然后两个链表一起走,一起走的过程中,两个链表第一次走到一起的那个节点就是第一个相交的节点。
例如:链表1 长度为100,链表2 长度为30,如果已经由步骤3 确定了链表1 和链表2 一定相交,那么接下来,链表1 先走70 步,然后链表1 和链表2 一起走,它们一定会共同进入第一个相交的节点。
问题二的具体实现,请参看如下代码中的 noLoop 方法。
public static Node noLoop(Node head1, Node head2) {if (head1 == null || head2 == null) {return null;}Node cur1 = head1;Node cur2 = head2;int n = 0;while (cur1.next != null) {n++;cur1 = cur1.next;}while (cur2.next != null) {n--;cur2 = cur2.next;}if (cur1 != cur2) {return null;}cur1 = n > 0 ? head1 : head2;cur2 = cur1 == head1 ? head2 : head1;n = Math.abs(n);while (n != 0) {n--;cur1 = cur1.next;}while (cur1 != cur2) {cur1 = cur1.next;cur2 = cur2.next;}return cur1;
}
问题三:如何判断两个有环链表是否相交,相交则返回第一个相交节点,不相交则返回null。
考虑问题三的时候,我们已经得到了两个链表各自的第一个入环节点,假设链表 1 的第一个入环节点记为loop1,链表2 的第一个入环节点记为loop2。以下是解决问题三的过程。
1.如果loop1==loop2,那么两个链表的拓扑结构如图2-8 所示。
这种情况下,我们只要考虑链表1 从头开始到loop1 这一段与链表2 从头开始到loop2 这一段,在那里第一次相交即可,而不用考虑进环该怎么处理,这就与问题二类似,只不过问题二是把null 作为一个链表的终点,而这里是把loop1(loop2)作为链表的终点。但是判断的主要过
程是相同的。
2.如果loop1!=loop2,两个链表不相交的拓扑结构如图2-9 所示。两个链表相交的拓扑结构如图2-10 所示。
如何分辨是这两种拓扑结构的哪一种呢?进入步骤3。
3.让链表1 从loop1 出发,因为loop1 和之后的所有节点都在环上,所以 将来一定能回到 loop1。
- 如果回到loop1 之前 并没有遇到loop2,说明两个链表的拓扑结构如图2-9 所示,也就是 不相交,直接返回 null;
- 如果回到loop1 之前 遇到了loop2,说明两个链表的拓扑结构如图2-10所示,也就是 相交。因为 loop1 和 loop2 都在两条链表上,只不过loop1 是离链表1 较近的节点,loop2 是离链表2 较近的节点。所以,此时返回loop1 或loop2 都可以。
问题三的具体实现参看如下代码中的 bothLoop 方法。
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {Node cur1 = null;Node cur2 = null;if (loop1 == loop2) {cur1 = head1;cur2 = head2;int n = 0;while (cur1 != loop1) {n++;cur1 = cur1.next;}while (cur2 != loop2) {n--;cur2 = cur2.next;}cur1 = n > 0 ? head1 : head2;cur2 = cur1 == head1 ? head2 : head1;n = Math.abs(n);while (n != 0) {n--;cur1 = cur1.next;}while (cur1 != cur2) {cur1 = cur1.next;cur2 = cur2.next;}return cur1;} else {cur1 = loop1.next;while (cur1 != loop1) {if (cur1 == loop2) {return loop1;}cur1 = cur1.next;}return null;}
}
附:完整代码
package chapter_2_listproblem;public class Problem_11_FindFirstIntersectNode {public static class Node {public int value;public Node next;public Node(int data) {this.value = data;}}public static Node getIntersectNode(Node head1, Node head2) {if (head1 == null || head2 == null) {return null;}Node loop1 = getLoopNode(head1);Node loop2 = getLoopNode(head2);if (loop1 == null && loop2 == null) {return noLoop(head1, head2);}if (loop1 != null && loop2 != null) {return bothLoop(head1, loop1, head2, loop2);}return null;}public static Node getLoopNode(Node head) {if (head == null || head.next == null || head.next.next == null) {return null;}Node n1 = head.next; // n1 -> slowNode n2 = head.next.next; // n2 -> fastwhile (n1 != n2) {if (n2.next == null || n2.next.next == null) {return null;}n2 = n2.next.next;n1 = n1.next;}n2 = head; // n2 -> walk again from headwhile (n1 != n2) {n1 = n1.next;n2 = n2.next;}return n1;}public static Node noLoop(Node head1, Node head2) {if (head1 == null || head2 == null) {return null;}Node cur1 = head1;Node cur2 = head2;int n = 0;while (cur1.next != null) {n++;cur1 = cur1.next;}while (cur2.next != null) {n--;cur2 = cur2.next;}if (cur1 != cur2) {return null;}cur1 = n > 0 ? head1 : head2;cur2 = cur1 == head1 ? head2 : head1;n = Math.abs(n);while (n != 0) {n--;cur1 = cur1.next;}while (cur1 != cur2) {cur1 = cur1.next;cur2 = cur2.next;}return cur1;}public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {Node cur1 = null;Node cur2 = null;if (loop1 == loop2) {cur1 = head1;cur2 = head2;int n = 0;while (cur1 != loop1) {n++;cur1 = cur1.next;}while (cur2 != loop2) {n--;cur2 = cur2.next;}cur1 = n > 0 ? head1 : head2;cur2 = cur1 == head1 ? head2 : head1;n = Math.abs(n);while (n != 0) {n--;cur1 = cur1.next;}while (cur1 != cur2) {cur1 = cur1.next;cur2 = cur2.next;}return cur1;} else {cur1 = loop1.next;while (cur1 != loop1) {if (cur1 == loop2) {return loop1;}cur1 = cur1.next;}return null;}}public static void main(String[] args) {// 1->2->3->4->5->6->7->nullNode head1 = new Node(1);head1.next = new Node(2);head1.next.next = new Node(3);head1.next.next.next = new Node(4);head1.next.next.next.next = new Node(5);head1.next.next.next.next.next = new Node(6);head1.next.next.next.next.next.next = new Node(7);// 0->9->8->6->7->nullNode head2 = new Node(0);head2.next = new Node(9);head2.next.next = new Node(8);head2.next.next.next = head1.next.next.next.next.next; // 8->6System.out.println(getIntersectNode(head1, head2).value);// 1->2->3->4->5->6->7->4...head1 = new Node(1);head1.next = new Node(2);head1.next.next = new Node(3);head1.next.next.next = new Node(4);head1.next.next.next.next = new Node(5);head1.next.next.next.next.next = new Node(6);head1.next.next.next.next.next.next = new Node(7);head1.next.next.next.next.next.next = head1.next.next.next; // 7->4// 0->9->8->2...head2 = new Node(0);head2.next = new Node(9);head2.next.next = new Node(8);head2.next.next.next = head1.next; // 8->2System.out.println(getIntersectNode(head1, head2).value);// 0->9->8->6->4->5->6..head2 = new Node(0);head2.next = new Node(9);head2.next.next = new Node(8);head2.next.next.next = head1.next.next.next.next.next; // 8->6System.out.println(getIntersectNode(head1, head2).value);}
}
输出结果:
6
2
4
左神算法:两个单链表相交的一系列问题(链表是否有环 / 两无环链表是否相交 / 两有环链表是否相交)相关推荐
- 两个单链表相交的一系列问题-Java
分享一个大牛的人工智能教程.零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请轻击http://www.captainbed.net package live.every.day.Pro ...
- 两个单链表相交的一系列问题
两个单链表相交的一系列问题 在本题中,单链表可能有环,也可能无环.给定两个单链表的头节点 head1 和 head2,这两个链表可能相交,也可能 不相交.请实现一个函数,如果两个链表相交,请返回相交的 ...
- 两个单链表相交的一系列问题----0_0
这道题emmm,难度相对来说很大额. 主要是要分成三种情况来考虑: 1. 两个链表都无环: 2. 两个链表都有环: 3. 一个有环一个无环,这种情况是不用 考虑的,一定没有交点(姥姥记住:单链表仅有一 ...
- 算法学习18-两个单链表相交的一系列问题
两个单链表相交的一系列问题 [题目] 在本题中,单链表可能有环,也可能无环.给定两个单链表的头节点 head1和head2,这两个链表可能相交,也可能不相交.请实现一个函数, 如果两个链表相交,请返回 ...
- 算法题:求两个单链表相交的第一个节点
目录 一.题目 思路 代码 二.总结 一.题目 #include <iostream> #include <stack> using namespace std; //链表结点 ...
- 链表问题11——两个单链表相交的系列问题(四):总结
题目 请实现一个函数,如果两个链表相交,请返回相交的第一个节点,如果不相交,返回null即可.链表可能有环或无环. 要求 如果链表1的长度为N,链表2的长度为M,时间复杂度达到O(M+N),额外空间复 ...
- 链表问题11——两个单链表相交的系列问题(三):判断两个有环链表是否相交
题目 判断两个有环链表是否相交,相交则返回第一个相交节点,否则返回null 在考虑此问题时,根据前面几篇文章的解法,我们已经得到了各自链表的入环节点,分别为loop1和loop2 思路 以下是问题三的 ...
- 链表问题11——两个单链表相交的系列问题(一):找到有环链表的环入口节点
题目 判断一个链表是否有环,如果有,则返回第一个进入环的节点,没有则返回null. 思路 如果一个链表没有环,那么遍历链表一定可以遇到链表的终点:如果链表有环,那么遍历链表就永远在环里转下去了.如何找 ...
- 链表问题11——两个单链表相交的系列问题(二):找到两个无环链表的交点
题目 判断两个无环链表是否相交,相交则返回第一个相交节点,否则返回null 思路 分别遍历链表1和链表2,最后一个节点分别即为end1和end2,长度分别记为len1和len2 如果end1不等于en ...
最新文章
- 微信小程序开发(2)_data属性
- hexo 博客支持PWA和压缩博文
- C++ 与 G++的区别
- CentOS 7 yum 安装php5.6
- 关于SQlserver数据库的加密应用
- Angular项目中核心模块core Module只加载一次的实现
- 迅雷游戏盒子下载|迅雷游戏盒子下载
- 注册围框html,一种可调模具围框的制作方法
- 语音识别的技术原理是什么?
- 物联卡认识易陷入的几大误区
- NLP之:百度SKEP
- SPI FLASH与NOR FLASH的区别
- 四阶行列式如何降阶_四阶行列式的计算方法
- matlab legend颜色不变,关于MATLAB画图中legend标注曲线颜色不匹配问题
- Microsoft visual studio安装2013
- 十分钟手把手教你设计简单易用的组件级考试题(单选、多选、填空、图片),建议收藏
- Tomcat学习之路
- fla文件与as文件之间的绑定
- python系列之---python的起源
- 细说影响淘宝排名的那些因素
热门文章
- 皇牌空战无限服务器,《皇牌空战:无限》正式停服 一个搏击长空的时代终结[多图]...
- 安卓进阶系列-06数据库框架(LitePal)的使用
- tomcat apache mysql_Android实现与Apache Tomcat服务器数据交互(MySql数据库)
- router vue 动态改变url_vue动态路由
- _variant_t和_bstr_t有什么区别
- 手把手教你玩转ARP包(一)
- OpenShift 与 OpenStack:让云变得更简单
- 干货:数据仓库架构及基础知识
- RabbitMQ脑裂
- Spring+Mybatis多数据源配置(一)——MySQL与Oracle通过配置切换