問題定義
兩個單向鏈表的頭指針,兩個鏈表都可能帶環
1: 判斷這兩個鏈表是否相交
2: 如果相交,給出他們相交的第一個節點。
無環情況
判斷鏈表是否相交
單鏈表相交,意味著相交結點具有相同的內存地址,且相交結點后的所有結點是兩個鏈表共有的。
方法一:
如果兩條單鏈表相交,則將鏈表B,連接到鏈表A后面,如圖所示(上面的鏈表是A,下面的鏈表是B),會形成環路,且鏈表B的表頭一定在環上。因此我們只需要從鏈表B開始遍歷,如果可以回到鏈表B的頭結點,則說明兩條鏈表相交。
時間復雜度:O(len(A)+len(B))
代碼如下:
// 結點
static class ListNode{
int value;
ListNode next;
}
static boolean isIntersect1(ListNode h1, ListNode h2){
boolean isinter = false;
ListNode p1 = h1, p2 = h2;
if(p1==null || h2==null) return false;
// find the end node of list p1
while(p1.next != null) p1 = p1.next;
// append list p2 on the tail of p1
p1.next = p2;
// enumerate list p2 from its header
while(p2 != null){
if(p2 == h2) {
isinter = true;
break;
}
p2 = p2.next;
}
return isinter;
}
方法二:
單鏈表相交,意味著相交結點具有相同的內存地址,且相交結點后的所有結點是兩個鏈表共有的。因此如果兩個鏈表相交,則最后一個節點肯定是相同的,因此只需要判斷兩個鏈表的最優一個節點是否相同。
時間復雜度: O(len(A)+len(B))
代碼如下:
static boolean isIntersect2(ListNode h1, ListNode h2){
ListNode p1 = h1, p2 = h2;
if(p1==null || h2==null) return false;
ListNode last1 = p1;
while(p1.next != null){
last1 = p1;
p1 = p1.next;
}
ListNode last2 = p2;
while (p2.next != null){
last1 = p2;
p2 = p2.next;
}
if(last1==last2){
return true;
}else return false;
}
尋找鏈表的第一個交點
先讓計算鏈表的長度,讓最長的鏈表A先走 len(A)-len(B)步,然后兩個鏈表一起走,第一個相交點,即為所求的點。
static ListNode findFisrtCrossNode(ListNode h1, ListNode h2){
int lenA = len(h1);
int lenB = len(h2);
ListNode p1 = h1, p2 = h2;
ListNode tmp = null;
if(lenA > lenB) tmp = p1;
else tmp = p2;
for(int i=0; i<Math.abs(lenA-lenB); ++i){
tmp = tmp.next;
}
if(lenA > lenB) p1=tmp;
else p2 = tmp;
while(p1!=null && p2!=null){
if(p1==p2) return p1;
p1 = p1.next;
p2 = p2.next;
}
return null;
}
static int len(ListNode h){
int clen = 0;
ListNode p = h;
while (p!=null){
p = p.next;
++clen;
}
return clen;
}
有環情況
判斷鏈表是否有環
追逐法:
設置兩個指針 fast, slow,將其初始化為鏈表的頭結點;然后兩個節點同時向前移動,fast一次移動2步,slow一次移動一步。如果存在環,fast指針和slow指針一定相遇。
static boolean hasCycle(ListNode h){
ListNode fast=h, slow=h;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if(fast==slow && slow!=null){
return true;
}
}
return false;
}
尋找環在鏈表上的入口點
當fast與slow相遇時,假設slow在環內循環了n次,fast在環內循環了m次,顯然n>m,且m為0 (若有環存在,fast 必然在slow繞環一周之前與slow相遇??紤]極端情況,slow走到環入口節點時,fast位于slow前面的一個節點,記為n0,此時fast以slow指針2倍的速度繞環,當fast指針追上slow指針時, nr/2v = mr/v ==> n/2 = m == n=2m, 即當slow繞環第一周后,回到環入口節點,n=2, 已經繞環兩周,并回到n0節點,由于fast指針步長等于slow的2倍,則fast指針和slow指針必然再環入口節點的前一個節點相遇)
如上圖所示,當slow指針和fast直接相遇時(定義此時的節點為相遇結點),相遇后,另p1指向頭結點,p2指向相遇節點,設頭結點距離環入口節點的距離:a, 頭結點距離相遇節點的距離: s=a+x, 環周長:r=x+y。讓p1、p2指針每次移動一步,當p1==p2時,此時就是p1(p2)指向的節點就是環入口節點。
假設在fast與slow重合時fast已繞環n周(n>0),且此時slow移動總長度為m,則fast移動總長度為2m。
2m = m+ nr = m + n(x+y) ==> m = n(x+y)
m = a+x
==> a+x = n(x+y)
==> a= n(x+y) - x = nr - x
指針p1從鏈表起點處開始遍歷,指針p2從相遇節點處開始遍歷,且p1和p2移動步長均為1。則當p1移動 a 步即到達環的入口點,由上式可知,此時p2也已移動 a 步即nr - x步。由于p2是從相遇節點處開始移動,故p2移動nr步是移回到了相遇節點處,再退 x 步則是到了環的入口點
該公式表明:從鏈表頭和相遇點分別設一個指針,每次各走一步,這兩個指針必定相遇,且相遇的第一個點為環入口點。
代碼如下:
static ListNode findCycleEntry(ListNode h){
ListNode fast=h, slow=h;
ListNode meetNode = null;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if(fast==slow && slow!=null){
meetNode = slow;
break;
}
}
ListNode p = h;
while(p!=null && meetNode!=null){
if(p==meetNode){
return p;
}
p = p.next;
meetNode = meetNode.next;
}
return null;
}
找出帶環的兩條鏈表相交的第一個節點
分兩種情況:
1、只有一條鏈表帶環,此時兩條鏈表不可能相交;否則,由于相交結點后的所有結點由兩條鏈表共享,因此導致另一條不帶環的鏈表卻出現環,導出相悖的結論。
2、兩條鏈表都帶環。如果兩條鏈表相交,則他們共享同一個環!
帶環鏈表相交,如圖所示,存在兩種情況:
1、交點在環中
2、交點不在環中
參考文獻中對該問題的解決辦法是首先找兩個鏈表的相遇點,但由于相遇點值存在于環中(利用fast,slow指針的方式得到的相遇點),因此其方法不能解決交點不在環中的情況。
解決方法:
第一步:分別找出兩個鏈表的環入口點pos1, pos2;
第二步:如果pos1==pos2, 屬于第二種情況:交點不在環中。然后以pos1作為兩條鏈表的終點,利用求不帶環單鏈表交點的方法求出交點。
第三步:如果pos1!=pos2, 從pos1開始遍歷環中的節點,如果沒有發現有節點與pos2相等,則說明兩條鏈表沒有交點,否則,存在交點
第四步:分別以pos1和pos2作為終止節點,用求不帶環單鏈表交點的方法求解。其中,必然一個有解,一個無解。取有解的那一組作為我們的答案。