本题来自左神《程序员代码面试指南》“两个单链表相交的一系列问题”题目。

题目

在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 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

左神算法:两个单链表相交的一系列问题(链表是否有环 / 两无环链表是否相交 / 两有环链表是否相交)相关推荐

  1. 两个单链表相交的一系列问题-Java

    分享一个大牛的人工智能教程.零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请轻击http://www.captainbed.net package live.every.day.Pro ...

  2. 两个单链表相交的一系列问题

    两个单链表相交的一系列问题 在本题中,单链表可能有环,也可能无环.给定两个单链表的头节点 head1 和 head2,这两个链表可能相交,也可能 不相交.请实现一个函数,如果两个链表相交,请返回相交的 ...

  3. 两个单链表相交的一系列问题----0_0

    这道题emmm,难度相对来说很大额. 主要是要分成三种情况来考虑: 1. 两个链表都无环: 2. 两个链表都有环: 3. 一个有环一个无环,这种情况是不用 考虑的,一定没有交点(姥姥记住:单链表仅有一 ...

  4. 算法学习18-两个单链表相交的一系列问题

    两个单链表相交的一系列问题 [题目] 在本题中,单链表可能有环,也可能无环.给定两个单链表的头节点 head1和head2,这两个链表可能相交,也可能不相交.请实现一个函数, 如果两个链表相交,请返回 ...

  5. 算法题:求两个单链表相交的第一个节点

    目录 一.题目 思路 代码 二.总结 一.题目 #include <iostream> #include <stack> using namespace std; //链表结点 ...

  6. 链表问题11——两个单链表相交的系列问题(四):总结

    题目 请实现一个函数,如果两个链表相交,请返回相交的第一个节点,如果不相交,返回null即可.链表可能有环或无环. 要求 如果链表1的长度为N,链表2的长度为M,时间复杂度达到O(M+N),额外空间复 ...

  7. 链表问题11——两个单链表相交的系列问题(三):判断两个有环链表是否相交

    题目 判断两个有环链表是否相交,相交则返回第一个相交节点,否则返回null 在考虑此问题时,根据前面几篇文章的解法,我们已经得到了各自链表的入环节点,分别为loop1和loop2 思路 以下是问题三的 ...

  8. 链表问题11——两个单链表相交的系列问题(一):找到有环链表的环入口节点

    题目 判断一个链表是否有环,如果有,则返回第一个进入环的节点,没有则返回null. 思路 如果一个链表没有环,那么遍历链表一定可以遇到链表的终点:如果链表有环,那么遍历链表就永远在环里转下去了.如何找 ...

  9. 链表问题11——两个单链表相交的系列问题(二):找到两个无环链表的交点

    题目 判断两个无环链表是否相交,相交则返回第一个相交节点,否则返回null 思路 分别遍历链表1和链表2,最后一个节点分别即为end1和end2,长度分别记为len1和len2 如果end1不等于en ...

最新文章

  1. 微信小程序开发(2)_data属性
  2. hexo 博客支持PWA和压缩博文
  3. C++ 与 G++的区别
  4. CentOS 7 yum 安装php5.6
  5. 关于SQlserver数据库的加密应用
  6. Angular项目中核心模块core Module只加载一次的实现
  7. 迅雷游戏盒子下载|迅雷游戏盒子下载
  8. 注册围框html,一种可调模具围框的制作方法
  9. 语音识别的技术原理是什么?
  10. 物联卡认识易陷入的几大误区
  11. NLP之:百度SKEP
  12. SPI FLASH与NOR FLASH的区别
  13. 四阶行列式如何降阶_四阶行列式的计算方法
  14. matlab legend颜色不变,关于MATLAB画图中legend标注曲线颜色不匹配问题
  15. Microsoft visual studio安装2013
  16. 十分钟手把手教你设计简单易用的组件级考试题(单选、多选、填空、图片),建议收藏
  17. Tomcat学习之路
  18. fla文件与as文件之间的绑定
  19. python系列之---python的起源
  20. 细说影响淘宝排名的那些因素

热门文章

  1. 皇牌空战无限服务器,《皇牌空战:无限》正式停服 一个搏击长空的时代终结[多图]...
  2. 安卓进阶系列-06数据库框架(LitePal)的使用
  3. tomcat apache mysql_Android实现与Apache Tomcat服务器数据交互(MySql数据库)
  4. router vue 动态改变url_vue动态路由
  5. _variant_t和_bstr_t有什么区别
  6. 手把手教你玩转ARP包(一)
  7. OpenShift 与 OpenStack:让云变得更简单
  8. 干货:数据仓库架构及基础知识
  9. RabbitMQ脑裂
  10. Spring+Mybatis多数据源配置(一)——MySQL与Oracle通过配置切换