【常见面试算法】相交链表
问题
在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 head1和head2,这两个链表可能相交,也可能不相交。请实现一个函数, 如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null 即可。 要求:如果链表1的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外空间复杂度请达到O(1)。
思路
解题思路:
1)如何判断单链表有环;
2)如何判断无环单链表的相交节点;
3)有环单链表的相交节点。
解题方法:
1)用Hashset判断是否有环,当hashset中查到已存在节点,则该节点为环的入口;
2)若不用hashset,准备快慢指针。快指针为空则无环,若有环,快慢指针一定会在环上相遇。相遇后,快指针回到头节点,变为一次走一步,则两个指针一定会在入口相遇。
3)若无环,如何判断相交的第一个节点?可以用hashset存list1的节点,遍历list2时去遍历hashset,若list2遍历完了还没查到,就不相交。若不用hashset,统计list1和list2的长度,以及两者end节点是否相等(内存地址是否相等,而不是值相等),若相等说明必相交,否则不相交。使较长的的list在两者长度差的地方开始遍历,最终找到相交节点。
两个有环链表的拓扑结构:
1) 各自为环,且不相交;
2) 先相交,再入环;
3) 环一样,但入口不同;
如何区别三种拓扑结构?
定义head1、head2、loop1、loop2,若loop1=loop2则为第二种,此时问题变回单链表相交问题,不用考虑环了;loop1不等于loop2,则可能为1也可能为3,此时,让loop1不断next,若找到等于loop2的节点,则为第三种,此时返回loop1或者loop2都是正确的,若loop1转了一圈还未找到loop2,则为第一种。
梳理:
1) 先判断链表是否有环,若一个有一个没有,返回null;
2) 若两个都无环,则为单链表问题;
若两个都有环,则先确定二者的环入口,再回到判断两个有环链表拓扑结构问题。
package 左神算法班实现.初级班.后四节课;
public class 链表相交 {
public static ListNode isCross(ListNode head1, ListNode head2){
if (head1 == null || head2 == null){
return null;
}
//判断是否为无环链表
ListNode loop1 = hasLoop(head1);
ListNode loop2 = hasLoop(head2);
if (loop1 == null && loop2 == null){//两个都是无环链表
return bothNotLoop(head1,head2);
}
if (loop1 != null && loop2 != null){
return bothLoop(head1, head2, loop1, loop2);
}
return null;
}
public static ListNode hasLoop(ListNode head){
if (head == null || head.next == null || head.next.next == null) return null;
ListNode fast = head.next.next;
ListNode slow = head.next;
while (fast != slow){
if (fast.next == null || fast.next.next == null){
return null;
}
slow = slow.next;
fast = fast.next.next;
}
fast = head;
while (fast != slow){
slow = slow.next;
fast = fast.next;
}
return fast;
}
public static ListNode bothNotLoop(ListNode head1, ListNode head2) {
if (head1 == null || head2 == null) return null;
ListNode h1 = head1;
ListNode h2 = head2;
int n = 0;
while (h1.next != null) {
n++;
h1 = h1.next;
}
while (h2.next != null) {
n--;
h2 = h2.next;
}
h1 = n > 0 ? head1 : head2;
h2 = h1 == head1 ? head2 :head1;
n = Math.abs(n);
while (n > 0){
n--;
h1 = h1.next;
}
while (h1 != h2){
h1 = h1.next;
h2 = h2.next;
}
return h1;
}
public static ListNode bothLoop(ListNode head1, ListNode head2, ListNode loop1, ListNode loop2){
ListNode cur1 = null;
ListNode 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;
while (cur1 != loop2){
cur1 = cur1.next;
if (cur1 == loop1){//loop1转了一圈还没有找到与loop2相等的节点,说明不是同一个环
return null;
}
}
return loop1;
}
}
public static void main(String[] args) {
// 1->2->3->4->5->6->7->null
ListNode head1 = new ListNode(1);
head1.next = new ListNode(2);
head1.next.next = new ListNode(3);
head1.next.next.next = new ListNode(4);
head1.next.next.next.next = new ListNode(5);
head1.next.next.next.next.next = new ListNode(6);
head1.next.next.next.next.next.next = new ListNode(7);
// 0->9->8->6->7->null
ListNode head2 = new ListNode(0);
head2.next = new ListNode(9);
head2.next.next = new ListNode(8);
head2.next.next.next = head1.next.next.next.next.next; // 8->6
System.out.println(isCross(head1, head2).val);
// 1->2->3->4->5->6->7->4...
head1 = new ListNode(1);
head1.next = new ListNode(2);
head1.next.next = new ListNode(3);
head1.next.next.next = new ListNode(4);
head1.next.next.next.next = new ListNode(5);
head1.next.next.next.next.next = new ListNode(6);
head1.next.next.next.next.next.next = new ListNode(7);
head1.next.next.next.next.next.next = head1.next.next.next; // 7->4
//System.out.println(hasLoop(head1).val);
// 0->9->8->2...
head2 = new ListNode(0);
head2.next = new ListNode(9);
head2.next.next = new ListNode(8);
head2.next.next.next = head1.next; // 8->2
System.out.println(isCross(head1, head2).val);
// 0->9->8->6->4->5->6..
head2 = new ListNode(0);
head2.next = new ListNode(9);
head2.next.next = new ListNode(8);
head2.next.next.next = head1.next.next.next.next.next; // 8->6
System.out.println(isCross(head1, head2).val);
}
}
查看21道真题和解析