经典面试题:链表的相交与环问题
生活随笔
收集整理的這篇文章主要介紹了
经典面试题:链表的相交与环问题
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
1、 給出兩個單向鏈表的頭指針pHead1和pHead2,判斷這兩個鏈表是否相交。假設(shè)兩個鏈表均不帶環(huán)。
?示意圖如下:
如果兩個鏈表相交于某一節(jié)點,那么在這個相交節(jié)點之后的所有節(jié)點都是兩個鏈表所共有的。也就是說,如果兩個鏈表相交,那么最后一個節(jié)點肯定是共有的。先遍歷第一個鏈表,記住最后一個節(jié)點,然后遍歷第二個鏈表,到最后一個節(jié)點時和第一個鏈表的最后一個節(jié)點做比較,如果相同,則相交,否則不相交。時間復(fù)雜度為O( len1 + len2),因為只需要一個額外指針保存最后一個節(jié)點地址,空間復(fù)雜度為O(1)。(編程之美上面有詳細的介紹)
2、給出一個單向鏈表的頭指針pHead,判斷鏈表中是否有環(huán)。
示意圖如下:
鏈表中有環(huán),其實也就是自相交。我們用兩個指針pslow和pfast從頭開始遍歷鏈表,pslow每次前進一個節(jié)點,pfast每次前進兩個結(jié)點,若存在環(huán),則pslow和pfast肯定會在環(huán)中相遇,若不存在,則pslow和pfast能正常到達最后一個節(jié)點(實際上是到達NULL)。
代碼如下:
// 判斷鏈表中是否有環(huán) bool IsExitLoop(LinkList *head) {LinkList *pslow = head;LinkList *pfast = head;while(pfast != NULL && pfast->next != NULL){pslow = pslow->next; // 每次前進一步pfast = pfast->next->next; // 每次前進二步if(pslow == pfast) // 兩個指針相遇,說明存在環(huán)return true;}return false; // 沒有環(huán) } 3、給出兩個單向鏈表的頭指針pHead1和pHead2,判斷這兩個鏈表是否相交,若相交返回第一個相交的節(jié)點。假設(shè)兩個鏈表均不帶環(huán)。
方法一:
判斷兩個鏈表中是否存在地址一致的節(jié)點,就可以知道是否相交了。可以對第一個鏈表的節(jié)點地址進行hash排序,建立hash表,然后針對第二個鏈表的每個節(jié)點的地址查詢hash表,如果它在hash表中出現(xiàn),則說明兩個鏈表有共同的結(jié)點。這個方法的時間復(fù)雜度為:O(max(len1+len2);但同時還得增加O(len1)的存儲空間存儲哈希表。這樣減少了時間復(fù)雜度,增加了存儲空間。
以鏈表節(jié)點地址為值,遍歷第一個鏈表,使用Hash保存所有節(jié)點地址值,結(jié)束條件為到最后一個節(jié)點(無環(huán))或Hash中該地址值已經(jīng)存在(有環(huán))。
方法二:
對第一個鏈表遍歷,計算長度len1,同時保存最后一個節(jié)點的地址。
對第二個鏈表遍歷,計算長度len2,同時檢查最后一個節(jié)點是否和第一個鏈表的最后一個節(jié)點相同,若不相同,則不相交,程序結(jié)束。
若相交,兩個鏈表均從頭節(jié)點開始,假設(shè)len1大于len2,那么將第一個鏈表先遍歷len1-len2個節(jié)點,此時兩個鏈表當(dāng)前節(jié)點到第一個相交節(jié)點的距離就相等了,比較下一個節(jié)點是不是相同,如果相同就返回該節(jié)點(即相交節(jié)點),若不相同,兩個鏈表都同步向后走一步,繼續(xù)比較。
示意圖如下:
方法三:
由于兩個鏈表都沒有環(huán),我們可以把第二個鏈表接在第一個鏈表的后面,如果得到的鏈表有環(huán),則說明這兩個鏈表相交。否則,這兩個鏈表不相交。這樣我們就把問題轉(zhuǎn)化為判斷一個鏈表是否有環(huán)了。最后,當(dāng)然可別忘記恢復(fù)原來的狀態(tài),去掉從第一個鏈表到第二個鏈表表頭的指向。
4、給出一個單向鏈表的頭指針pHead,判斷鏈表中是否有環(huán),若存在,則求出進入環(huán)中的第一個節(jié)點。
?示意圖如下:
紅色虛線框中的節(jié)點為待求節(jié)點。
首先使用第2個題目中的快、慢指針來判斷鏈表是否存在環(huán),若不存在結(jié)束。
若鏈表中存在環(huán),我們從鏈表頭、與兩個指針的相遇點分別設(shè)一個指針,每次各走一步,兩個指針必定相遇,且相遇的第一個點為環(huán)的入口點。
代碼如下:
// 找到環(huán)的第一個入口點 LinkList* FindLoopPort(LinkList *head) {LinkList *pslow = head;LinkList *pfast = head;while(pfast != NULL && pfast->next != NULL){pslow = pslow->next; // 每次前進一步pfast = pfast->next->next; // 每次前進二步if(pslow == pfast) // 兩個指針相遇,說明存在環(huán)break;}if(pfast == NULL || pfast->next == NULL) // 不存在環(huán)return NULL;pslow = head;while(pslow != pfast){pslow = pslow->next; // 每次前進一步pfast = pfast->next; // 每次前進一步}return pslow; // 返回環(huán)的入口點 } 分析:當(dāng)pfast若與pslow相遇時,pslow肯定沒有走遍歷完鏈表,而pfast已經(jīng)在環(huán)內(nèi)循環(huán)了n圈(1<=n)。假設(shè)pslow走了s步,則pfast走了2s步(pfast步數(shù)還等于s 加上在環(huán)上多轉(zhuǎn)的n圈),設(shè)環(huán)長為r,則:
2s = s + nr s= nr
設(shè)整個鏈表長L,入口環(huán)與相遇點距離為x,起點到環(huán)入口點的距離為a。 a + x = nr 則 a + x = (n – 1)r +r = (n-1)r + L - a a = (n-1)r + (L – a – x)
(L – a – x)為相遇點到環(huán)入口點的距離,由此可知,從鏈表頭到環(huán)入口點等于(n-1)循環(huán)內(nèi)環(huán)+相遇點到環(huán)入口點,于是我們從鏈表頭、與相遇點分別設(shè)一個指針,每次各走一步,兩個指針必定相遇,且相遇第一點為環(huán)入口點。
小結(jié):鏈表是數(shù)據(jù)結(jié)構(gòu)中最基本的,也是面試中常考的,與鏈表相關(guān)的題目也變化多端,只要基礎(chǔ)扎實,多積累一些處理類似問題的技巧,面試時便能應(yīng)對自如。
單鏈表的的歸并排序,同樣需要找到鏈表的中間節(jié)點,可以使用前面的這個快、慢指針的方法。
typedef struct LNode {int data;struct LNode *next; }LNode , *LinkList;// 對兩個有序的鏈表進行遞歸的歸并 LinkList MergeList_recursive(LinkList head1 , LinkList head2) {LinkList result;if(head1 == NULL)return head2;if(head2 == NULL)return head1;if(head1->data < head2->data){result = head1;result->next = MergeList_recursive(head1->next , head2);}else{result = head2;result->next = MergeList_recursive(head1 , head2->next);}return result; }// 對兩個有序的鏈表進行非遞歸的歸并 LinkList MergeList(LinkList head1 , LinkList head2) {LinkList head , result = NULL;if(head1 == NULL)return head2;if(head2 == NULL)return head1;while(head1 && head2){if(head1->data < head2->data){if(result == NULL){head = result = head1;head1 = head1->next;}else{result->next = head1;result = head1;head1 = head1->next;}}else{if(result == NULL){head = result = head2;head2 = head2->next;}else{result->next = head2;result = head2;head2 = head2->next;}}}if(head1)result->next = head1;if(head2)result->next = head2;return head; }// 歸并排序,參數(shù)為要排序的鏈表的頭結(jié)點,函數(shù)返回值為排序后的鏈表的頭結(jié)點 LinkList MergeSort(LinkList head) {if(head == NULL)return NULL;LinkList r_head , slow , fast;r_head = slow = fast = head;// 找鏈表中間節(jié)點的兩種方法/*while(fast->next != NULL){if(fast->next->next != NULL){slow = slow->next;fast = fast->next->next;}elsefast = fast->next;}*/while(fast->next != NULL && fast->next->next != NULL){slow = slow->next;fast = fast->next->next;}if(slow->next == NULL) // 鏈表中只有一個節(jié)點return r_head;fast = slow->next;slow->next = NULL;slow = head;// 函數(shù)MergeList是對兩個有序鏈表進行歸并,返回值是歸并后的鏈表的頭結(jié)點//r_head = MergeList_recursive(MergeSort(slow) , MergeSort(fast));r_head = MergeList(MergeSort(slow) , MergeSort(fast));return r_head; }
?示意圖如下:
如果兩個鏈表相交于某一節(jié)點,那么在這個相交節(jié)點之后的所有節(jié)點都是兩個鏈表所共有的。也就是說,如果兩個鏈表相交,那么最后一個節(jié)點肯定是共有的。先遍歷第一個鏈表,記住最后一個節(jié)點,然后遍歷第二個鏈表,到最后一個節(jié)點時和第一個鏈表的最后一個節(jié)點做比較,如果相同,則相交,否則不相交。時間復(fù)雜度為O( len1 + len2),因為只需要一個額外指針保存最后一個節(jié)點地址,空間復(fù)雜度為O(1)。(編程之美上面有詳細的介紹)
2、給出一個單向鏈表的頭指針pHead,判斷鏈表中是否有環(huán)。
示意圖如下:
鏈表中有環(huán),其實也就是自相交。我們用兩個指針pslow和pfast從頭開始遍歷鏈表,pslow每次前進一個節(jié)點,pfast每次前進兩個結(jié)點,若存在環(huán),則pslow和pfast肯定會在環(huán)中相遇,若不存在,則pslow和pfast能正常到達最后一個節(jié)點(實際上是到達NULL)。
代碼如下:
// 判斷鏈表中是否有環(huán) bool IsExitLoop(LinkList *head) {LinkList *pslow = head;LinkList *pfast = head;while(pfast != NULL && pfast->next != NULL){pslow = pslow->next; // 每次前進一步pfast = pfast->next->next; // 每次前進二步if(pslow == pfast) // 兩個指針相遇,說明存在環(huán)return true;}return false; // 沒有環(huán) } 3、給出兩個單向鏈表的頭指針pHead1和pHead2,判斷這兩個鏈表是否相交,若相交返回第一個相交的節(jié)點。假設(shè)兩個鏈表均不帶環(huán)。
方法一:
判斷兩個鏈表中是否存在地址一致的節(jié)點,就可以知道是否相交了。可以對第一個鏈表的節(jié)點地址進行hash排序,建立hash表,然后針對第二個鏈表的每個節(jié)點的地址查詢hash表,如果它在hash表中出現(xiàn),則說明兩個鏈表有共同的結(jié)點。這個方法的時間復(fù)雜度為:O(max(len1+len2);但同時還得增加O(len1)的存儲空間存儲哈希表。這樣減少了時間復(fù)雜度,增加了存儲空間。
以鏈表節(jié)點地址為值,遍歷第一個鏈表,使用Hash保存所有節(jié)點地址值,結(jié)束條件為到最后一個節(jié)點(無環(huán))或Hash中該地址值已經(jīng)存在(有環(huán))。
方法二:
對第一個鏈表遍歷,計算長度len1,同時保存最后一個節(jié)點的地址。
對第二個鏈表遍歷,計算長度len2,同時檢查最后一個節(jié)點是否和第一個鏈表的最后一個節(jié)點相同,若不相同,則不相交,程序結(jié)束。
若相交,兩個鏈表均從頭節(jié)點開始,假設(shè)len1大于len2,那么將第一個鏈表先遍歷len1-len2個節(jié)點,此時兩個鏈表當(dāng)前節(jié)點到第一個相交節(jié)點的距離就相等了,比較下一個節(jié)點是不是相同,如果相同就返回該節(jié)點(即相交節(jié)點),若不相同,兩個鏈表都同步向后走一步,繼續(xù)比較。
示意圖如下:
方法三:
由于兩個鏈表都沒有環(huán),我們可以把第二個鏈表接在第一個鏈表的后面,如果得到的鏈表有環(huán),則說明這兩個鏈表相交。否則,這兩個鏈表不相交。這樣我們就把問題轉(zhuǎn)化為判斷一個鏈表是否有環(huán)了。最后,當(dāng)然可別忘記恢復(fù)原來的狀態(tài),去掉從第一個鏈表到第二個鏈表表頭的指向。
4、給出一個單向鏈表的頭指針pHead,判斷鏈表中是否有環(huán),若存在,則求出進入環(huán)中的第一個節(jié)點。
?示意圖如下:
紅色虛線框中的節(jié)點為待求節(jié)點。
首先使用第2個題目中的快、慢指針來判斷鏈表是否存在環(huán),若不存在結(jié)束。
若鏈表中存在環(huán),我們從鏈表頭、與兩個指針的相遇點分別設(shè)一個指針,每次各走一步,兩個指針必定相遇,且相遇的第一個點為環(huán)的入口點。
代碼如下:
// 找到環(huán)的第一個入口點 LinkList* FindLoopPort(LinkList *head) {LinkList *pslow = head;LinkList *pfast = head;while(pfast != NULL && pfast->next != NULL){pslow = pslow->next; // 每次前進一步pfast = pfast->next->next; // 每次前進二步if(pslow == pfast) // 兩個指針相遇,說明存在環(huán)break;}if(pfast == NULL || pfast->next == NULL) // 不存在環(huán)return NULL;pslow = head;while(pslow != pfast){pslow = pslow->next; // 每次前進一步pfast = pfast->next; // 每次前進一步}return pslow; // 返回環(huán)的入口點 } 分析:當(dāng)pfast若與pslow相遇時,pslow肯定沒有走遍歷完鏈表,而pfast已經(jīng)在環(huán)內(nèi)循環(huán)了n圈(1<=n)。假設(shè)pslow走了s步,則pfast走了2s步(pfast步數(shù)還等于s 加上在環(huán)上多轉(zhuǎn)的n圈),設(shè)環(huán)長為r,則:
2s = s + nr s= nr
設(shè)整個鏈表長L,入口環(huán)與相遇點距離為x,起點到環(huán)入口點的距離為a。 a + x = nr 則 a + x = (n – 1)r +r = (n-1)r + L - a a = (n-1)r + (L – a – x)
(L – a – x)為相遇點到環(huán)入口點的距離,由此可知,從鏈表頭到環(huán)入口點等于(n-1)循環(huán)內(nèi)環(huán)+相遇點到環(huán)入口點,于是我們從鏈表頭、與相遇點分別設(shè)一個指針,每次各走一步,兩個指針必定相遇,且相遇第一點為環(huán)入口點。
小結(jié):鏈表是數(shù)據(jù)結(jié)構(gòu)中最基本的,也是面試中常考的,與鏈表相關(guān)的題目也變化多端,只要基礎(chǔ)扎實,多積累一些處理類似問題的技巧,面試時便能應(yīng)對自如。
單鏈表的的歸并排序,同樣需要找到鏈表的中間節(jié)點,可以使用前面的這個快、慢指針的方法。
typedef struct LNode {int data;struct LNode *next; }LNode , *LinkList;// 對兩個有序的鏈表進行遞歸的歸并 LinkList MergeList_recursive(LinkList head1 , LinkList head2) {LinkList result;if(head1 == NULL)return head2;if(head2 == NULL)return head1;if(head1->data < head2->data){result = head1;result->next = MergeList_recursive(head1->next , head2);}else{result = head2;result->next = MergeList_recursive(head1 , head2->next);}return result; }// 對兩個有序的鏈表進行非遞歸的歸并 LinkList MergeList(LinkList head1 , LinkList head2) {LinkList head , result = NULL;if(head1 == NULL)return head2;if(head2 == NULL)return head1;while(head1 && head2){if(head1->data < head2->data){if(result == NULL){head = result = head1;head1 = head1->next;}else{result->next = head1;result = head1;head1 = head1->next;}}else{if(result == NULL){head = result = head2;head2 = head2->next;}else{result->next = head2;result = head2;head2 = head2->next;}}}if(head1)result->next = head1;if(head2)result->next = head2;return head; }// 歸并排序,參數(shù)為要排序的鏈表的頭結(jié)點,函數(shù)返回值為排序后的鏈表的頭結(jié)點 LinkList MergeSort(LinkList head) {if(head == NULL)return NULL;LinkList r_head , slow , fast;r_head = slow = fast = head;// 找鏈表中間節(jié)點的兩種方法/*while(fast->next != NULL){if(fast->next->next != NULL){slow = slow->next;fast = fast->next->next;}elsefast = fast->next;}*/while(fast->next != NULL && fast->next->next != NULL){slow = slow->next;fast = fast->next->next;}if(slow->next == NULL) // 鏈表中只有一個節(jié)點return r_head;fast = slow->next;slow->next = NULL;slow = head;// 函數(shù)MergeList是對兩個有序鏈表進行歸并,返回值是歸并后的鏈表的頭結(jié)點//r_head = MergeList_recursive(MergeSort(slow) , MergeSort(fast));r_head = MergeList(MergeSort(slow) , MergeSort(fast));return r_head; }
總結(jié)
以上是生活随笔為你收集整理的经典面试题:链表的相交与环问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 二分搜索及其扩展(循环递增数组的搜索)
- 下一篇: 有序数组求中位数问题