引言
其實這道題目早在劍指Offer上就已經出現過,參見以前的這篇文章,但后來July前輩也有一片文章講過類似問題,對比之下,之前的解法還不夠全面,沒有考慮到所有情況,這篇文章把這道題目作一個梳理。
題目總結起來是這樣:
輸入兩個鏈表,判斷它們是否有公共節點,返回bool。
引申:找出它們的第一個公共節點的指針,如果沒有公共節點則返回NULL。
鏈表的定義如下:
struct ListNode{ int val; ListNode* next; ListNode(int x) : val(x), next(NULL) {}; };
輸出函數:
bool JudgeCommonListNode(ListNode* head1, ListNode* head2){ } ListNode* FindFirstCommonListNode(ListNode* head1, ListNode* head2){ } //引申,求第一個公共節點
首先需要考慮空指針輸入,有一個輸入為NULL,就不會有交點。
尋找兩鏈表的第一個交點,簡便的做法是求出兩鏈表長度差diff后,讓長的鏈表先走diff步,然后依次比較兩鏈表節點,若相同則返回。
但這一個思路的前提是:兩個鏈表都不含環。
因此,在考慮完空指針后,需要檢驗兩個鏈表是否含有環。
(1) 若一個有環一個無環,則肯定沒有交點。
(2) 若都無環,可以用上面的思路求出第一個交點;如果只要求返回bool,則比較一下兩鏈表的尾節點是否相等即可。
(3) 兩鏈表都含有環的情況下,如果只需要返回bool值,我們就檢驗其中一個鏈表的環入口是否在另一個鏈表的環上,在環上就返回true,表明相交,不在環上返回false。
如果要返回第一個交點呢?這時候我們可以分情況考慮:如果兩個鏈表都是“勺型”,所謂勺型,就是指不是純的環鏈表。那這兩個勺型鏈表如果有交點,環部分肯定是已經百分百重疊了,第一個交點只能出現在“勺柄”上,既然是勺柄,自然就無環啦,因此,上面那個求兩個無環鏈表的第一個交點的方法有可以拿來用了~
如果兩個鏈表都有環,而且至少有一個是純環鏈表,那么如果有交點,這個純環鏈表必然所有節點都和另一個鏈表的環部分重合。此時,我們把那個純環鏈表的輸入節點(假設是head1)看作第一個交點,因此,只需要驗證一下head1是否在另一個鏈表的環上即可。在的話就返回head1,不在就返回NULL。
因此這道題目其實並不簡單,它包含“判斷鏈表是否含有環”,“求環入口節點”,“求兩條無環鏈表的第一個交點” 三個子問題。
這里給出引申問題,也就是求第一個交點的題目的代碼,輔助函數traverse 的功能包含判斷是否有環,返回環入口節點(無環則返回NULL),以及求頭結點距離環入口節點(有環情況)或者尾節點(無環情況)的長度,這個長度被存在len變量里。
在判斷是否有環的過程中,需要注意單節點環鏈表的情況。
ListNode* traverse(ListNode* head, int &len, bool &circled){ ListNode* p1 = head -> next, *p2 = head -> next; int step = 1; for(; p2 != NULL; ++step, p1 = p1 -> next, p2 = p2 -> next){ p2 = p2 -> next; ++step; if(!p2 || p1 == p2) break; } if(!p2){ //無環 len = step; //用len記錄鏈表長度 circled = false; return NULL; }else{//有環 circled = true; len = 0; //用len記錄頭結點到環入口距離 for(p1 = head; p1 != p2; p1 = p1 -> next, p2 = p2 -> next, ++len); return p1; } } ListNode* FindFirstCommonListNode(ListNode* head1, ListNode* head2){ if(!head1 || !head2) return NULL; if(head1 == head2) return head1; int len1 = 0, len2 = 0; bool cc1 = false, cc2 = false; ListNode* CcleEnt1 = traverse(head1, len1, cc1); ListNode* CcleEnt2 = traverse(head2, len2, cc2); if((!cc1 && cc2) || (cc1 && !cc2)) return NULL; //若一個有環,一個無環,則肯定沒有交點 if(len1 > 0 && len2 > 0){ //當兩鏈表都無環,或者都有環且首節點都不在環上時 ListNode *st1 = (len1 > len2 ? head1 : head2); ListNode *st2 = (len1 > len2 ? head2 : head1); ListNode *cce1 = (len1 > len2 ? CcleEnt1 : CcleEnt2); ListNode *cce2 = (len1 > len2 ? CcleEnt2 : CcleEnt1); for(int i = 0; i < (len1 - len2 > 0 ? len1 - len2 : len2 - len1); ++i, st1 = st1 -> next); for(; st1 != cce1 && st2 != cce2 && (st1 != st2); st1 = st1 -> next, st2 = st2 -> next); if(st1) return st1; return NULL; }else{ //len1, len2 中有一個為0 說明其中至少有一條鏈表是純環鏈表。 ListNode *st1 = (len1 == 0 ? head1 : head2); //選擇那個純環鏈表的head,驗證它是否在另一個鏈表的環上,在的話它就是第一個交點,不在的話就沒有交點。 ListNode *st2 = (len1 == 0 ? head2 : head1); ListNode *p = st2 -> next; for(; p != st2 && p != st1; p = p -> next); if(p == st1) return st1; return NULL; } }
帶測試用例的代碼:

#include <iostream> using namespace std; struct ListNode{ int val; ListNode* next; ListNode(int x) : val(x), next(NULL) {}; }; ListNode* CreateListNode(int value) { ListNode* pNode = new ListNode(value); return pNode; } void ConnectListNodes(ListNode* pCurrent, ListNode* pNext) { if(pCurrent == NULL) { cout << "Error to connect two nodes.\n"; } pCurrent -> next = pNext; } ListNode* traverse(ListNode* head, int &len, bool &circled){ ListNode* p1 = head -> next, *p2 = head -> next; int step = 1; //cout << "---------\n"; for(; p2 != NULL; ++step, p1 = p1 -> next, p2 = p2 -> next){ //只有一個節點的環鏈表 //cout << "p2: " << p2 -> val << endl; p2 = p2 -> next; ++step; if(!p2 || p1 == p2) break; } //cout << "---------\n"; if(!p2){ //無環 len = step; //用len記錄鏈表長度 circled = false; cout << "circled: " << (circled ? "Yes" : "No") << "; length: " << len << endl; return NULL; }else{//有環 circled = true; len = 0; //用len記錄頭結點到環入口距離 for(p1 = head; p1 != p2; p1 = p1 -> next, p2 = p2 -> next, ++len); cout << "circled: " << (circled ? "Yes" : "No") << "; length: " << len << endl; return p1; } } ListNode* JudgeCommonListNode(ListNode* head1, ListNode* head2){ if(!head1 || !head2) return NULL; if(head1 == head2) return head1; int len1 = 0, len2 = 0; bool cc1 = false, cc2 = false; ListNode* CcleEnt1 = traverse(head1, len1, cc1); ListNode* CcleEnt2 = traverse(head2, len2, cc2); if((!cc1 && cc2) || (cc1 && !cc2)) return NULL; //若一個有環,一個無環,則肯定沒有交點 if(len1 > 0 && len2 > 0){ //當兩鏈表都無環,或者都有環且首節點都不在環上時 ListNode *st1 = (len1 > len2 ? head1 : head2); ListNode *st2 = (len1 > len2 ? head2 : head1); ListNode *cce1 = (len1 > len2 ? CcleEnt1 : CcleEnt2); ListNode *cce2 = (len1 > len2 ? CcleEnt2 : CcleEnt1); for(int i = 0; i < (len1 - len2 > 0 ? len1 - len2 : len2 - len1); ++i, st1 = st1 -> next); for(; st1 != cce1 && st2 != cce2 && (st1 != st2); st1 = st1 -> next, st2 = st2 -> next); if(st1) return st1; return NULL; }else{ //len1, len2 中有一個為0 說明其中至少有一條鏈表是純環鏈表。 ListNode *st1 = (len1 == 0 ? head1 : head2); //選擇那個純環鏈表的head,驗證它是否在另一個鏈表的環上,在的話它就是第一個交點,不在的話就沒有交點。 ListNode *st2 = (len1 == 0 ? head2 : head1); ListNode *p = st2 -> next; for(; p != st2 && p != st1; p = p -> next); if(p == st1) return st1; return NULL; } } /**---------測試代碼----------*/ ListNode* CreateList1(){ ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(2); ListNode* pNode3 = CreateListNode(3); ListNode* pNode4 = CreateListNode(4); ListNode* pNode5 = CreateListNode(5); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); return pNode1; } ListNode* CreateCc1(){ ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(2); ListNode* pNode3 = CreateListNode(3); ListNode* pNode4 = CreateListNode(4); ListNode* pNode5 = CreateListNode(5); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); ConnectListNodes(pNode5, pNode2); return pNode1; } int main(){ // ListNode* head1 = CreateList1(); // ListNode* head2 = CreateCc1(); // ListNode* res = JudgeCommonListNode(head1, head2); // ListNode* pNode1 = CreateListNode(1); // ListNode* pNode2 = CreateListNode(2); // ListNode* pNode3 = CreateListNode(3); // ConnectListNodes(pNode1, pNode2); // ConnectListNodes(pNode3, pNode2); // ListNode* res = JudgeCommonListNode(pNode1, pNode3); // // ListNode* pNode1 = CreateListNode(1); // ListNode* pNode2 = CreateListNode(2); // ConnectListNodes(pNode1, pNode2); // ListNode* res = JudgeCommonListNode(pNode1, pNode2); // // ListNode* pNode1 = CreateListNode(1); // ListNode* pNode2 = CreateListNode(2); // ConnectListNodes(pNode1, pNode1); // ConnectListNodes(pNode2, pNode2); // ListNode* res = JudgeCommonListNode(pNode1, pNode2); // ListNode* pNode1 = CreateListNode(1); // ListNode* pNode2 = CreateListNode(2); // ListNode* pNode3 = CreateListNode(3); // ListNode* pNode4 = CreateListNode(4); // ListNode* pNode5 = CreateListNode(5); // ListNode* pNode6 = CreateListNode(5); // ConnectListNodes(pNode1, pNode2); // ConnectListNodes(pNode2, pNode3); // ConnectListNodes(pNode3, pNode4); // ConnectListNodes(pNode4, pNode5); // ConnectListNodes(pNode6, pNode3); ListNode* pNode1 = CreateListNode(1); ListNode* pNode2 = CreateListNode(2); ListNode* pNode3 = CreateListNode(3); ListNode* pNode4 = CreateListNode(4); ListNode* pNode5 = CreateListNode(5); ListNode* pNode6 = CreateListNode(6); ListNode* pNode7 = CreateListNode(7); ListNode* pNode8 = CreateListNode(8); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); ConnectListNodes(pNode5, pNode6); ConnectListNodes(pNode6, pNode3); ConnectListNodes(pNode7, pNode8); ConnectListNodes(pNode8, pNode2); //ListNode* res = JudgeCommonListNode(pNode1, pNode7); //ListNode* res = JudgeCommonListNode(pNode7, pNode3); ListNode* res = JudgeCommonListNode(pNode1, pNode5); if(res) cout << "First overlap is: " << res -> val << endl; else cout << "Not overlap" << endl; return 0; }