兩個單鏈表相交的一系列問題
【題目】在本題中,單鏈表可能有環,也可能無環。給定兩個單鏈表的頭節點head1和head2,這兩個鏈表可能相交,也可能不相交。請實現一個函數,如果兩個鏈表相交,則返回相交的第一個節點;如果不相交,則返回null。
【要求】如果鏈表1的長度爲N,鏈表2的長度爲M,時間複雜度請達到O(N+M),額外空間複雜度請達到O(1)
思考
其實這道題目是一系列的問題,拆分下來如下
1. 判斷單鏈表是否成環
2. 對於成環的單鏈表,找出第一個入環的點
3. 判斷兩個環是否相交
所以我們先一步一步的實現。
單鏈表是否成環
首先我們可以通過藉助外部數據結構的方式來完成。
用Hash結構,遍歷鏈表,如果hash表中沒有值,那麼將該節點放入hash表中。如果單鏈表成環,那麼在遍歷的過程中,肯定會遇到hash表中已存在節點的情況。
如果不借助外部數據結構,那麼我們可以通過快慢雙指針的方式來判斷
一個快指針,一個慢指針,快指針一次走兩步,慢指針一次走一步,如果鏈表成環,那麼兩個指針一定會相遇的
入環的第一個節點
第一種方式當然還是藉助外部數據結構了
用Hash結構,遍歷鏈表並放入hash表中,放入之前先判斷是否存在,如果出現了第一個存在的節點,那麼它就是第一個入環的節點了
第二種方式同樣是使用快慢指針,但同時需要藉助一個公式
如果鏈表成環,那麼快慢指針一定會相遇,那麼在相遇的時候,快指針回到head節點,此時慢節點到入環點的距離等於head節點到入環點的距離
相交情況
兩個鏈表尋找相交點可以分爲三種情況:
1. 兩個鏈表都不成環,那麼就直接判斷最後一個節點,如果最後一個節點相同,那麼就肯定相交了
2. 兩個鏈表都成環,在入環之前相交
3. 兩個鏈表都成環,在分別成環之後相交,那麼第一個相交點可以看作各自的入環點
所以這裏都需要分情況來判斷的
第一種情況很好判斷是否相交,那麼怎麼獲取第一個交點呢? 我們可以通過計數的方式,這樣可以得知哪一個鏈表更長以及連鏈表的長度差值N,然後再先將長鏈表移動N步,最後兩個鏈表一起移動,那麼相遇的那個地方就是第一個交點了
對於第二種情況,如果在入環之前就相交了,那麼它們的入環點就是相同的;第三種情況,入環點則不同
實現
判斷單鏈表是否成環
public class FindFirstIntersectNode {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
/**
* 判斷單鏈表是否成環
* 藉助外部數據結構
*/
public static boolean isLoopNode1(Node head) {
HashSet<Node> hash = new HashSet<Node>();
while(head != null) {
if(hash.contains(head)) {
return true;
}
hash.add(head);
head = head.next;
}
return false;
}
/**
* 通過快慢指針的方式判斷單鏈表是否成環
*/
public static boolean isLoopNode2(Node head) {
if(head == null || head.next == null || head.next.next == null) {
return false;
}
Node fast = head.next.next;
Node slow = head.next;
while(fast != slow) {
if(fast.next == null || fast.next.next == null) {
return false;
}
fast = fast.next.next;
slow = slow.next;
}
return true;
}
}
獲取第一個入環的節點
public class FindFirstIntersectNode {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
public static Node getLoopNode(Node head) {
HashSet<Node> hash = new HashSet<Node>();
while(head != null) {
if(hash.contains(head)) {
return head;
}
hash.add(head);
head = head.next;
}
return null;
}
public static Node getLoopNode2(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node n1 = head.next; // n1 -> slow
Node n2 = head.next.next; // n2 -> fast
while (n1 != n2) {
if (n2.next == null || n2.next.next == null) {
return null;
}
n2 = n2.next.next;
n1 = n1.next;
}
n2 = head; // n2 -> walk again from head
while (n1 != n2) {
n1 = n1.next;
n2 = n2.next;
}
return n1;
}
}
終極判斷
public class FindFirstIntersectNode {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
public static Node findFirstIntersectNode(Node head1, Node head2) {
if(head1 == null || head2 == null) {
return null;
}
Node loopNode1 = getLoopNode(head1);
Node loopNode2 = getLoopNode(head2);
// no loop
if(loopNode1 == null && loopNode2 == null) {
return noLoop(head1, head2);
}
// before loopNode
if(loopNode1 != null && loopNode2 != null) {
return loopNode1;
}
return null;
}
public static Node getLoopNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node n1 = head.next; // n1 -> slow
Node n2 = head.next.next; // n2 -> fast
while (n1 != n2) {
if (n2.next == null || n2.next.next == null) {
return null;
}
n2 = n2.next.next;
n1 = n1.next;
}
n2 = head; // n2 -> walk again from head
while (n1 != n2) {
n1 = n1.next;
n2 = n2.next;
}
return n1;
}
public static Node noLoop(Node head1, Node head2) {
Node cur1 = head1;
Node cur2 = head2;
int n = 0;
while(cur1 != null) {
cur1 = cur1.next;
n++;
}
while(cur2 != null) {
cur2 = cur2.next;
n--;
}
if(cur1 != cur2) {
return null;
}
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;
}
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
Node cur1 = null;
Node 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.next;
while (cur1 != loop1) {
if (cur1 == loop2) {
return loop1;
}
cur1 = cur1.next;
}
return null;
}
}
}