题目:给定两个可能有环也可能无环的单链表,头节点分别为head1和head2.请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回null
要求:如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度达到O(1)
这个题目堪称链表的爹题,能手写出这一道题基本上一般大厂的链表笔试面试就没有问题了
解这个题目之前建议看一下我的另外一个面试题的题解:
链表:常见面试题-Leetcode原题142环形链表II_鱼跃鹰飞的博客-CSDN博客
这个题目的解体分析,两个链表有以下几种情况
(1)两个链表都无环,这种情况是有可能相交的,相交的时候从相交点开始之后的每一个点都是相同的。
(2)一个有环,一个无环,肯定不会有相交节点,因为环肯定是链表的最后的那些节点,一旦相交后面节点都相同,不可能出现相交还一个有环一个无环的情况。
(3)两个都有环,这个分为两种大情况:相交和不相交 。相交又分为两种:入环节点是同一个节点和入环节点不是同一个节点(入环两个节点都可以认为是第一个相交节点,返回哪个都行)
代码实现如下:
package dataStructure.linkedList.practice;
import dataStructure.linkedList.ListNode;
public class FindFirstIntersect {
/**
* 返回两个链表的第一个相交节点
* @param head1 第一个链表的头节点
* @param head2 第二个链表的头节点
* @return 第一个相交节点
*/
public static ListNode findFirstIntersectNode(ListNode head1, ListNode head2) {
//先找到每一个链表的第一个入环节点
ListNode firstLoop1 = getLoopNode(head1);
ListNode firstLoop2 = getLoopNode(head2);
/**
* 两个链表有以下集中情况
* 1.两个链表都没有环,是有可能相交的
* 2.链表1有环,链表2无环,不可能相交,因为相交之后所有的节点都会重合,有环到了入环节点之后就不可能出去了
* 3.链表1无环,链表2有环,不可能相交,因为相交之后所有的节点都会重合,有环到了入环节点之后就不可能出去了
* 4.两个都有环,是有可能相交的
*/
if(firstLoop1 == null && firstLoop2 == null) {
return getFirstIntersectNoLoop(head1, head2);
} else if(firstLoop1 != null && firstLoop2 != null) {
return getFirstIntersectAllLoop(head1, head2, firstLoop1, firstLoop2);
} else {
return null;
}
}
/**
* 确定两个链表都有环的情况下寻找第一个相遇的节点
* @param head1 第一个链表的头节点
* @param head2 第二个链表的头节点
* @return
*/
public static ListNode getFirstIntersectAllLoop(ListNode head1, ListNode head2, ListNode firstLoop1, ListNode firstLoop2) {
//如果两个链表的第一个入环节点一样,说明一定相交,这个时候就需要找到哪个节点是第一个相交节点
if(firstLoop1 == firstLoop2) {
//把firstLoop1(也是firstLoop2)当成终点,与两个无环链表的做法一样寻找起点
ListNode cur1 = head1;
ListNode cur2 = head2;
int count = 0;
while(cur1 != firstLoop1) {
cur1 = cur1.next;
count ++;
}
while(cur2 != firstLoop1) {
cur2 = cur2.next;
count --;
}
cur1 = count >= 0? head1 : head2;
cur2 = cur1 == head1? head2 : head1;
count = Math.abs(count);
while(count > 0) {
cur1 = cur1.next;
count --;
}
while(cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
} else {
//如果第一个入环节点不一样,分为两种情况:1.两者不相交 2.两者相交只是入环节点不同
ListNode node1 = firstLoop1.next;
//两个链表是否相交
boolean isIntersected = false;
while(node1 != firstLoop1) {
if(node1 == firstLoop2) {
//如果node1从firstLoop1开始走一直再走回来的过程中遇到了firstLoop2设置isIntersected为true
//如果一直未遇到设置isIntersected为false,表示两个链表不相交
isIntersected = true;
break;
}
//node1沿着环依次移动
node1 = node1.next;
}
if(isIntersected) {
//如果入环节点不同并相交说明两者就是相交在环上,返回两者入环节点哪个都行
return firstLoop1;
} else {
return null;
}
}
}
/**
* 确定两个链表都无环的情况下寻找第一个相遇的节点
* @param head1
* @param head2
* @return
*/
public static ListNode getFirstIntersectNoLoop(ListNode head1, ListNode head2) {
int count = 0;
ListNode cur1 = head1;
ListNode cur2 = head2;
while(cur1 != null) {
count ++;
cur1 = cur1.next;
}
while(cur2 != null) {
count --;
cur2 = cur2.next;
}
cur1 = count >= 0? head1 : head2;
cur2 = cur1 == head1? head2 : head1;
count = Math.abs(count);
while(count > 0) {
cur1 = cur1.next;
count --;
}
while(cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
/**
* 获取链表的第一个入环节点,无环返回null
* @param head 链表的头节点
* @return
*/
public static ListNode getLoopNode(ListNode head) {
if(head == null || head.next == null || head.next.next == null) {
return null;
}
//使用快慢指针,快指针一次跳两个节点,慢指针一次跳一个节点
ListNode fast = head.next.next;
ListNode slow = head.next;
//如果slow和fast没有相遇
while(slow != fast) {
//如果fast已经为空,或者它的next为空,不管哪个为空,都说明无环
if(fast == null || fast.next == null) {
//无环返回null
return null;
}
//快指针一次跳两个节点
fast = fast.next.next;
//慢指针一次跳一个节点
slow = slow.next;
}
//走到这里(跳出循环且没有返回)说明确实有环
//fast退回head位置,每次走一步
fast = head;
//跳出循环一定是第一次相遇的节点
while(slow != fast) {
slow = slow.next;
fast = fast.next;
}
//返回fast或者slow都行
return slow;
}
public static void main(String[] args) {
ListNode n1 = new ListNode(1);
ListNode n2 = new ListNode(2);
ListNode n3 = new ListNode(3);
ListNode n4 = new ListNode(4);
ListNode n5 = new ListNode(5);
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = n5;
n5.next = n3;
ListNode n6 = new ListNode(6);
n6.next = n4;
/*ListNode firstLoop = getLoopNode(n1);
System.out.println(firstLoop.value);*/
ListNode first = findFirstIntersectNode(n1, n6);
System.out.println(first == null? null : first.val);
ListNode n7 = new ListNode(7);
ListNode n8 = new ListNode(8);
ListNode n9 = new ListNode(9);
n7.next = n8;
n8.next = n9;
n9.next = n8;
first = findFirstIntersectNode(n1, n7);
System.out.println(first == null? null : first.val);
/**
* 测试第一个入环节点 [3,2,0,-4]
*/
ListNode n11 = new ListNode(3);
ListNode n12 = new ListNode(2);
ListNode n13 = new ListNode(0);
ListNode n14 = new ListNode(-4);
n11.next = n12;
n12.next = n13;
n13.next = n14;
n14.next = n12;
ListNode firstLoopTest = getLoopNode(n11);
System.out.println(firstLoopTest == null ? null : firstLoopTest.val);
}
}
应该是有Leetcode原题了,忘了是哪个了,代码只是为了实现功能,比较粗略,大家看不明白的可以私信我,发现错误也欢迎批评指正。