判断两个单链表是否相交以及相交的情况下求第一个相交节点,两个链表可以有环,也可以无环。因此我们可以从以下几个步骤分析:
- 判断一个单链表是否存在环路,如果无环,返回null,反之,返回入环节点。
- 如果两个单链表均无环,进入相应的子程序,如果无相交,返回null,反之,返回第一个相交节点。
- 如果一个有环,一个无环,进入相应的子程序,如果无相交,返回null,反之,返回第一个相交节点。
- 如果两个单链表均有环,进入相应的子程序,如果无相交,返回null,反之,返回第一个相交节点。
接下来,我们结合图片以及相应的理论对这四个小问题求解。
-
判断一个单链表是否存在环路
如下图所示,如果单链表存在环路,则只可能是图一所示的结构,请读者自行收起野马般的思路,类似图二的结构是不存在的。
上图中的入环节点为节点2。
接下来,我们分析一下判断单链表是否有环,如果有环,返回入环节点的思路。
首先,有两种解决方案,一种情况算法空间复杂度为O(N),另一种空间复杂度O(1),读者可以根据不同的情形择优选取。
第一种:使用HashSet,因为HashSet只能容纳不重复的元素,所以从头遍历链表,将经过的每一个节点都加进去,如果发现有重复,则直接返回第一个重复的节点,就是入环节点。如果没有重复,说明链表无环,返回null。
很明显,这个算法空间复杂度为O(N),在此不展示代码,因为较为简单,读者有兴趣可以自己尝试,接下来讲述第二种方法,空间复杂度为O(1)。第二种:使用快慢指针。快指针一次走两步,慢指针一次走一步,很明显,当快指针遇到null走不动的时候,就可以确定链表无环,直接返回null。
如果链表存在环路,可以得到一个结论是快指针在有限的步数内肯定会追上慢指针并且相遇(敲黑板),读者可以自行画图尝试,是一个很容易观察出来的结论。
下一步,在快慢指针相遇的一刻,将快指针指向头节点,然后让快慢指针同时走,并都走一步,注意,这里是都走一步并且保持同步。下面,见证奇迹的时刻到了,当快慢节点再次相遇时,二者此时都指向入环节点,也就是两个指针肯定都会在入环节点处首次相遇。这个结论可以用数学归纳法证明,笔者不罗列出证明过程,读者记住这个结论就可以编码了,并且肯定正确。下面是代码。
/** * 判断一个链表是否有环,如果有环返回入环节点,如果无环,返回null。 * 步骤: * ①准备两个快慢指针,一个一次走一步,一个一次走两步,如果有环,两个指针肯定会相遇,如果快指针走到了null,则代表无环; * ②当两个指针相遇后,将快指针指向头节点,然后两个指针每次都同时走一步,当两个指针相等时,就到了链表的入环节点,返回即可。 * @param head * @return */ private static Node getLoopNode(Node head) { if(head.next == null || head.next.next == null) { return null; } Node slow = head.next; Node fast = head.next.next; while(slow != fast) { if(fast.next == null || fast.next.next == null) { return null; } slow = slow.next; fast = fast.next.next; } //达到这一步之后,两个指针相遇