在上一篇文檔中,通過java實現了單鏈表反轉的問題,之后發現一個更有意思的問題就是如何判斷兩個鏈表是否相交?如果相交,則需要得到交點。
對于這個問題,需要分別考慮鏈表上是否存在環的情況。
//鏈表節點
public class DataNode {
private int data;
private DataNode next;
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public DataNode getNext() {
return next;
}
public void setNext(DataNode next) {
this.next = next;
}
public DataNode(int data) {
this.data = data;
}
}
1.兩個鏈表都不存在環
對于這種情況,如果兩個鏈表相交,又都不存在環,那么不難想象這兩個鏈表共同構成了一個Y型。因此,只要分別遍歷這兩個鏈表,找到尾端節點,判斷尾端節點是否相同即可確認是否相交。
如果要求這種情況的交點,由于相交部分全部都相同,因此,只需要先得到兩個鏈表的差,用兩個指針分別指向這兩個鏈表P1,P2假定P1與P2相差為N,那么將P1移動N個節點后,P1與P2同時出發,第一個相等的節點即為交點。
/**
* 無環情況下,判斷兩個鏈表是否相交,只需要遍歷鏈表,判斷尾節點是否相等即可。
* @param h1
* @param h2
* @return
*/
public static boolean isJoinNoLoop(DataNode h1,DataNode h2) {
DataNode p1 = h1;
DataNode p2 = h2;
while(null != p1.getNext())
p1 = p1.getNext();
while(null != p2.getNext())
p2 = p2.getNext();
return p1 == p2;
}
/**
* 無環情況下找到第一個相交點
* 方法: 算出兩個鏈表的長度差為x,長鏈表先移動x步,之后兩鏈表同時移動,直到相遇的第一個交點。
* @param h1
* @param h2
* @return
*/
public static DataNode getFirstJoinNode(DataNode h1,DataNode h2) {
int length1 = 0;
int length2 = 0;
while(null != h1.getNext()) {
length1 ++;
h1 = h1.getNext();
}
while(null != h2.getNext()) {
length1 ++;
h2 = h2.getNext();
}
return length1>=length2?getNode(h1,length1,h2,length2):getNode(h2,length2,h1,length1);
}
這是最樂觀的一種情況,但是還需要考慮鏈表上存在環的情況。那么還需要添加一個方法,判斷鏈表上是否存在環。
對于如何判斷鏈表上是否存在環,解決辦法是采用快慢指針,兩個指針P1、P2分別指向同一個鏈表的頭節點,之后,P1一次前進兩個節點,P2一次前進一個節點。如果最終P1和P2能重合,則說明一定存在交點。反之如果最終P1或者P2存在一個為空的情況,則說明這兩個鏈表不相交。
/**
* 判斷是否存在環
* 步驟:設置兩個指針同時指向head,其中一個一次前進一個節點(P1),另外一個一次前進兩個節點(P2)。
* p1和p2同時走,如果其中一個遇到null,則說明沒有環,如果走了N步之后,二者指向地址相同,那么說明鏈表存在環。
* @param h
* @return
*/
public static boolean isLoop(DataNode h) {
DataNode p1 = h;
DataNode p2 = h;
while(p2.getNext() != null && p2.getNext().getNext()!=null){
p1 = p1.getNext();
p2 = p2.getNext().getNext();
if(p1 == p2)
break;
}
return !(p1==null||p2==null);
}
因此上述問題還有另外一個解法,將其中一個鏈表首尾相連,檢測另外一個鏈表是否存在環,如果存在(該環就是首尾相連的鏈表),則兩個鏈表相交,而檢測出來的依賴環入口即為相交的第一個點。需找出環的入口,設置p1,p2兩個指針,同樣一個走一步一個走兩步,兩者相遇則必在環上某一點相遇,記下此位置p1=p2,在p1和p2重合后,設置一個p3指向表頭,然后p1和p3每次同時行走一步,每步前進一個節點,等到p1和p3重合時,重合的位置就是環的入口。
參考csdn上的一張圖:
設L1為無環長度,L2為環長,a為兩指針相遇時慢速指針在環上走過的距離,而且a一定小于環總長L2(這是因為當慢速指針剛進入環時,快速指針已經在環中,且距離慢速指針的距離最長為L2-1,需要追趕的距離為L2-1,即剛好在慢速指針的下一個節點,需要幾乎一整圈的距離來追趕,趕上時,慢速指針也不能走完一圈)。此時設慢速指針走過的節點數為N,則可列出:
快速指針走過的節點數為: 2N = L1 + k * L2 + a; (這里快速指針走過的節點數一定是慢速指針走過的2倍)。
慢速指針走過的節點數為: N = L1 + a;
則相減可得, N = k * L2 , 于是得到 k * L2 = L1 + a; 即, L1 = (k-1) * L2 + (L2 - a) (這里k至少是大于等于1的,因為快速指針至少要多走一圈)
即 L1的長度 = 環長的整數倍 + 相遇點到入口點的距離, 此時設置頭結點p3, 與p1同時,每次都走一步,相遇點即為入口點。
/**
* 方法二: 將其中一個鏈表首尾相連 從另外一個鏈表開始,檢測是否存在環,如果存在,則說明二者相交。
* 如果需要找出環的入口,則設P1 P2 兩個指針,P1一次走兩步,P2一次走一步,兩者在環上某一點相遇。記下此位置。
* 此時設置一個指針P3指向表頭,然后P1和P3每次同時行走一步,每步前進一個節點。等到P1、P3重合時,則重合位置即使環入口。
* @param h1
* @param h2
* @return
*/
public DataNode entryNoLoop(DataNode h1,DataNode h2) {
DataNode p = h1;
while(null != p.getNext()){
p = p.getNext();
}
p.setNext(h1);
return entryLoop(h2);
}
/**
* 獲取環的入口點
* @param h
* @return
*/
public DataNode entryLoop(DataNode h) {
DataNode p1 = h;
DataNode p2 = h;
DataNode p3 = h;
while(null != p2.getNext() && null != p2.getNext().getNext()){
p1 = p1.getNext();
p2 = p2.getNext().getNext();
if(p1 == p2)
break;
}
while(p3 != p1) {
p1 = p1.getNext();
p3 = p3.getNext();
}
return p3;
}
2.兩個鏈表均存在環
對于連個鏈表均存在環的情況,相交點要么在環上,要么在環外。
無論上述何種情況,均需要首先分別找到各自到環的入口點。解法可以即使上述entryLoop方法。
在得到環的入口點之后,各自判斷環的入口點是否相同,如果如口點相同,則為左圖描述情況,因此只需計算著兩個鏈表到入口點部分長度之差,然后用長的部分減去差,再同時與短的部分同步前進,如果節點相同,則為相交點。反之如果入口點不同,則相交點為這兩個鏈表的任意一個入口點。
/**
*
* @param h1
* 鏈表1的頭節點
* @param l1
* 鏈表1的環入口
* @param h2
* 鏈表2的頭節點
* @param l2
* 鏈表2的頭節點
* @return
*/
public static DataNode bothLoop(DataNode h1, DataNode l1, DataNode h2, DataNode l2) {
DataNode p1 = null;
DataNode p2 = null;
if (l1 == l2) {
p1 = h1;
p2 = h2;
int n = 0;
while (p1 != l1) {
n++;
p1 = p1.getNext();
}
while (p2 != l2) {
n--;
p2 = p2.getNext();
}
p1 = n > 0 ? h1 : h2;
p2 = p1 == h1 ? h2 : h1;
n = Math.abs(n);
while (n != 0) {
n--;
h1 = h1.getNext();
}
while (p1 != p2) {
p1 = p1.getNext();
p2 = p2.getNext();
}
return p1;
} else {
p1 = l1.getNext();
while (p1 != l1) {
if (p1 == l2) {
return l1;
}
}
return null;
}
}
/**
*
* @param h1
* @param h2
* @return
*/
public DataNode getJoinNode(DataNode h1, DataNode h2) {
if (null == h1 || null == h2)
return null;
DataNode l1 = entryLoop(h1);
DataNode l2 = entryLoop(h2);
if (null == l1 && null == l2)
return getFirstJoinNode(h1, h2);
if (null != l1 && null != l2)
return bothLoop(h1,l1,h2,l2);
return null;
}