题目描述:给定两个可能有环也可能无环的单链表,头节点head1和head2,请实现一个函数,如果两个链表相交,返回相交的第一个节点,如果不相交,返回 null 。
目录
要解决的第一个问题,判断链表是否有环
方法一:哈希表
时间复杂度 O(N) ,空间复杂度 O( N )
public ListNode isLoop(ListNode head){
HashSet set = new HashSet();
ListNode p = head;
while(p != null){
if(set.contains(p)){
return p;
}else{
set.add(p);
}
p = p.next;
}
return null;
}
方法二:快慢指针
力扣官方证明:
疑点记录:
为什么 fast 和 slow 指针一定会相遇,而不是路过呢?
fast 指针一次走两步, slow 指针一次走一步,所以 fast 指针的追赶速度就是 一次一步。追赶速度并没有跳步,所以不会路过。
为什么在 slow 指针入环的第一圈就会相遇呢?
假设环长度为 a
把入环节点当作起点,slow 位于起点,最坏情况:如果此时 fast 指针在走了 n 圈之后来到了起点的下一个节点,这时追赶距离为 a - 1,因为追赶速度为 1,所以追赶时间 为 a - 1,也就是在 a - 1 时间后,fast 指针追上了 slow 指针,而 slow 指针一次走一步,在 a - 1 的时间里,slow 指针也走了 a - 1 步,不足以圈,所以在 slow 指针入环的第一圈就会相遇
代码:
public ListNode isLoop(ListNode head){
if(head == null || head .next == null || head.next.next == null){
return null;
}
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;//说明无环
}
//有环,将 fast 指针置于头部,和 slow 指针每次都走一步,相遇时即为入环节点
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}
遇到的坑:
上面代码我们时将 head 节点作为 fast 和 slow 的共同出发点,在第一次相遇后将 fast 节点置于出发点 head。
但如果我们将 head 的前一个节点(虚拟节点)作为出发点,初始化为如下:
ListNode fast = head.next;
ListNode slow = head;
这时我们在将 fast 节点重新置于出发点时就不能置于 head 节点了,fast 节点我们重新给的位置是共同的出发节点,而我们初始时的出发节点是 head 的前一个节点(虚拟节点),所以我们要将 fast 指向虚拟节点。
如下:
public ListNode isLoop(ListNode head){
if(head == null || head .next == null || head.next.next == null){
return null;
}
ListNode fast = head.next;
ListNode slow = head;
while(fast != null && fast.next != null) {
if(fast == slow){
break;
}
fast = fast.next.next;
slow = slow.next;
}
if(fast == null || fast.next == null){
return null;//说明无环
}
//有环,将 fast 指针置于头部,和 slow 指针每次都走一步,相遇时即为入环节点
//定义虚拟节点pre
ListNode pre = new ListNode();
if(fast == slow){
pre.next = head;
while(pre != slow){
pre = pre.next;
slow = slow.next;
}
}
return pre;
}
解决完是否有环后,就能处理相交问题了
- 两个链表都没环
两个链表都没环代码实现:
public ListNode noLoop(ListNode headA,ListNode headB){
int n = 0;
ListNode p1 = headA;
ListNode p2 = headB;
while(p1 != null){
n++;
p1 = p1.next;
}
while(p2 != null){
n--;
p2 = p2.next;
}
// n 就是headA 的长度 减去 headB 的长度
ListNode cur = n > 0 ? headA : headB;
ListNode other = cur == headA ? headB : headA;
n = Math.abs(n);
while(n-- > 0){
cur = cur.next;
}
while( cur != null){
if(cur == other){
break;
}
cur = cur.next;
other = other.next;
}
return cur;
}
- 一个有环,一个没环( 这种情况不可能相交 )
- 两个链表都有环
情况二逻辑与 noLoop 相似
两个链表都有环代码实现:
public ListNode bothLoop(ListNode headA,ListNode loop1,ListNode headB,ListNode loop2){
ListNode p1 = headA;
ListNode p2 = headB;
if(loop1 == loop2){
int n = 0;
while(p1 != loop1){
n++;
p1 = p1.next;
}
while(p2 != loop2){
n--;
p2 = p2.next;
}
ListNode cur = n > 0 ? headA : headB;
ListNode other = cur == headA ? headB : headA;
while(n-- > 0){
cur = cur.next;
}
while(cur != other){
cur = cur.next;
other = other.next;
}
return cur;
}else{
p1 = loop1.next;
while(p1 != loop1){
if(p1 == loop2){
return p1;
}
p1 = p1.next;
}
return null;
}
}
最终代码:
public ListNode getIntersectNode(ListNode head1,ListNode head2){
if(head1 == null || head2 == null){
return null;
}
ListNode loop1 = isLoop(head1);
ListNode loop2 = isLoop(head2);
//两个都没环
if(loop1 == null && loop2 == null){
return noloop(head1,head2);
}
//两个都有环
if(loop1 != null && loop2 != null){
return bothLoop(head1,loop1,head2,loop2);
}
//其他情况不可能相交
return null;
}