一、問題描述
有兩個鏈表,判斷是否相交並求出相交的點?
二、問題分析
大家看到題目會不由自主的想起一個很普遍的情況,就是下面
但是這個題目有一個陷阱就是,沒有講明兩個鏈表的結構,沒有很好地給出,其實有三種情況
(1)當兩個鏈表都無環,如上面
(2)當一個鏈表有環,另一個鏈表無環
(3)當兩個鏈表都有環
這三種情況,下面一一講解這些情況下兩個鏈表是否相交以及相交點。
三、問題解析
(一)當兩個鏈表都無環
3.1 方法1
鏈表相交后,發現后面的結點全部公用,我們可以這樣:從兩個鏈表的頭走到尾,判斷鏈尾地址信息是不是一樣,如果是則相交,反之則不交即可。
Node* LinkList::findByIndex(int index){ //根據索引返回節點信息 Node* p = head; int i = 0; if (index<0||index >getLength()) { cout << "索引非法!" << endl; return NULL; } while (p) { if (i == index) return p; else { p = p->next; i++; } } return NULL; } bool LinkList::isIntersect(LinkList preLinkList, LinkList forLinkList) { Node* tail1 = preLinkList.findByIndex(preLinkList.head->value);//鏈表1尾部 Node* tail2 = forLinkList.findByIndex(forLinkList.head->value);//鏈表2尾部 if (tail1 == tail2) { return true; } return false; }
3.2 方法二
我們可以根據上圖,相交之后部分的長度是一樣的,相等的,所以我們可以這樣讓長鏈表的長度減去稍微較短的長度,得到兩個鏈表的相差長度,再讓長鏈表從頭結點開始遍歷這個長度,與此同時,短鏈表也向后走,若指針相等,鏈表就相交,反之,則不交
bool LinkList::isIntersect(LinkList preLinkList, LinkList forLinkList) { bool flag = false; if (preLinkList.head->value > forLinkList.head->value) flag = true; int length = abs(preLinkList.head->value - forLinkList.head->value);//相差的長度 Node* p= preLinkList.head->next, *q= forLinkList.head->next;//此處初始化應對length=0的情況 if (length) { if (flag) {//第一個鏈表長 p = preLinkList.findByIndex(length)->next; q = forLinkList.head->next; } else { p = forLinkList.findByIndex(length)->next; q = preLinkList.head->next; } } while (p != q) { //若指針相等跳出 ,可能會同時為空 p = p->next; q = q->next; } if (p) //排序跳出時為空 return true; else return false; }
求相交點
Node* LinkList::findIntersectPoint(LinkList preLinkList, LinkList forLinkList){ //不帶環的鏈表求交點 bool flag = false; if (preLinkList.head->value > forLinkList.head->value) flag = true; int length = abs(preLinkList.head->value - forLinkList.head->value);//相差的長度 Node* p= preLinkList.head->next, *q= forLinkList.head->next;//此處初始化應對length=0的情況 if (length) { if (flag) {//第一個鏈表長 p = preLinkList.findByIndex(length)->next; q = forLinkList.head->next; } else { p = forLinkList.findByIndex(length)->next; q = preLinkList.head->next; } } while (p != q) { //若指針相等跳出 ,可能會同時為空 p = p->next; q = q->next; } if (p) //排序跳出時為空 return p; else return NULL; }
(二 )當一個鏈表不帶環,另一個帶環
如果一個鏈表不帶環,另一個鏈表帶環,兩個鏈表一定沒有相交點。因為相交之后,鏈表肯定有部分是共用,若有環,都會帶環,而且環的長度一樣,相交點也是環的入口處。
(3)兩個都帶環
就有兩種情況,一種交於環外,另外是交與環內,如下圖
圖1
圖2
通過上圖2發現,如果把環看成鏈表1的,則鏈表2相交於B點,反過來把環看出鏈表2的,則鏈表1相交於A處,所以你看的B處和A處,只是視覺上的,如果環是兩個鏈表的,則相交於環的任何結點上。
思路:
(1)因為環是大家共用的,我們先求出兩個鏈表的差L,也就是環外的差,並獲得鏈表的環入口點
(2)讓一個較長的鏈表從開始走過L個結點
(3)然后再讓短的鏈表從頭開始與之同步移動,保證兩個鏈表不走到環的入口時,判斷結點地址是否相同
(4)若出現相同而且未到入口,則返回交點,交於環外
(5)反之,直接返回鏈表的入口即可。
Node* LinkList::findIntersectPoint(LinkList preLinkList, LinkList forLinkList){ //帶環鏈表求交點 int length1 = preLinkList.getCircleLinklistLength(); int length2 = forLinkList.getCircleLinklistLength(); //獲得每個鏈表總長度 Node* preEntry = preLinkList.findEnterCircle(); Node* forEntry = forLinkList.findEnterCircle(); //找到每個的環入口點 int length; Node* pre = preLinkList.head->next; Node* forth = forLinkList.head->next; //初始化各自鏈表的第一個節點的指針 if (length1 > length2) { //根據誰長誰短 決定誰后移指針 length = length1 - length2; for (int i = 1;i <= length;pre = pre->next, i++); } else { length = length2 - length1; for (int i = 1;i <= length;forth = forth->next, i++); } while (pre != preEntry&&forth != forEntry&&pre != forth) { //向后移,注意是否走到環入口點 pre = pre->next; forth = forth->next; } /*結束循環的3種情況 1.=====2個鏈表任意一個或全部同時走到了環入口點,結束了循環 2.======2個鏈表任意一個或全部同時走到了環入口點,並且pre=forth,結束了循環 3.======沒有到達任何一個環入口點,pre=forth結束了循環 */ if (pre == forth) //對應情況2、3,如果是情況2,說明2個鏈表的環入口點相同;如果情況3,則說明在任何一個環入口之前找到了相交點 return pre; else return preEntry;//對應情況1 }
以上就是鏈表的相交和相交點的思考,准備去BAT面試的大佬,還是可以讀一下的。