题目:
在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 head1 和 head2,这两个链表可能相交,也可能不相交。请实现一个函数,如果两个链表相交,请返回相交的第一个节点;如果不相交,返回 null 即可。
要求:如果链表1和2的长度分别为M、N,时间复杂度请达到O(M+N),额外空间复杂度达到O(1)
分析:
本题需要拆分成三个子问题进行讨论:
1. 如何判断一个链表是否有环,如果有,则返回第一个进入环的节点,没有则返回null。
2. 如何判断两个无环链表是否相交,相交则返回第一个相交的节点,不相交则返回null。
3. 如何判断两个有环链表是否相交,相交返回第一个相交点,不相交返回null。
首先解决第一问题。这个问题是 leetcode 141、142,具体思路可看网上说明
1 public Node getLoopNode(Node head) 2 { 3 if(head == null || head.next == null || head.next.next == null) 4 { 5 return null; 6 } 7 Node slow = head.next, fast = head.next.next; 8 while(fast != slow) 9 { 10 if(fast.next == null || fast.next.next == null) 11 return null; 12 slow = slow.next; 13 fast = fast.next.next; 14 } 15 16 fast = head; 17 while(fast != slow) 18 { 19 fast = fast.next; 20 slow = slow.next 21 } 22 return slow; 23 }
解决了问题一后我们就知道了两个链表有环或无环的情况。如果一个链表有环,另一个无环,那么这两个链表无论如何也不可能相交。能相交的情况就两种,一种是两个链表都无环即问题二;另一个两个链表都有环,即问题三。
问题二的解法:
1. 遍历两个链表获取链表的长度len1、len2,同时记录两个链表最后一个节点end1、end2。
2. 如果 end1 != end2,说明不相交返回null即可,如果相等进入第4步。
3. 如果链表1比2长则表1先走 len1 - len2 步,反之表2先走 len2 - len1 步。两个链表第一次走到一起的那个节点即为第一个相交点。
1 public Node noLoop(Node head1, Node head2) 2 { 3 if(head1 == null || head2 == null) 4 return null; 5 6 Node cur1 = head1, cur2 = head2; 7 int len = 1; 8 while(cur1 != null) 9 { 10 cur = cur.next; 11 len++; 12 } 13 while(cur2 != null) 14 { 15 cur2 = cur2.next; 16 len--; 17 } 18 19 cur1 = len > 0 ? head1 : head2; 20 cur2 = cur1 == head1 ? head2 : head1; 21 len = Math.abs(len); 22 while(len != 0) 23 { 24 len--; 25 cur1 = cur1.next; 26 } 27 28 while(cur1 != cur2) 29 { 30 cur1 = cur1.next; 31 cur2 = cur2.next; 32 } 33 return cur1; 34 }
问题三:
1. 如果 loop1 = loop2,那么两个链表形式如图
类似于问题二,只不过我们将loop1(loop2)作为终点。
2. 如果 loop1 != loop2,两个链表相交如图
让表1从 loop1 出发,如果 loop1 之前并没有遇到 loop2,说明两个链表是不相交的,如果遇到了 loop2则说明两链表结构如图,此时返回loop1或loop2都可以
1 public Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) 2 { 3 Node cur1 = null, cur2 = null; 4 5 if(loop1 == loop2) 6 { 7 cur1 = head1; 8 cur2 = head2; 9 int len = 0; 10 while(cur1 != loop1) 11 { 12 len++; 13 cur1 = cur1.next; 14 } 15 while(cur2 != loop2) 16 { 17 len--; 18 cur2 = cur2.next; 19 } 20 cur1 = n > 0 ? head1 : head2; 21 cur2 = cur1 == head1 ? head2 : head1; 22 len = Math.abs(len); 23 while(len != 0) 24 { 25 len--; 26 cur1 = cur.next; 27 } 28 while(cur1 != cur2) 29 { 30 cur1 = cur1.next; 31 cur2 = cur2.next 32 } 33 return cur1; 34 } 35 else 36 { 37 cur1 = loop1.next; 38 while(cur1 != loop1) 39 { 40 if(cur1 == loop2) 41 { 42 return loop1; 43 } 44 cur1 = cur1.next; 45 } 46 } 47 return null; 48 }
结合上述三种情况,总代码如下
1 class Node 2 { 3 public int data; 4 public Node next; 5 6 public Node(int data) 7 { 8 this.data = data; 9 } 10 } 11 12 public Node getIntersectNode(Node head1, Node head2) 13 { 14 if(head1 == null || head2 == null) 15 return null; 16 Node loop1 = getLoopNode(head1); 17 Node loop2 = getLoopNode(head2); 18 19 if(loop1 == null && loop2 == null) 20 { 21 return noLoop(head1, head2); 22 } 23 else if(loop1 != null && loop2 != null) 24 { 25 return bothLoop(head1, loop1, head2, loop2); 26 } 27 return null; 28 }
参考资料:程序员代码面试指南 IT名企算法与数据结构题目最优解,左程云