劍指offer——鏈表相關問題總結
首先統一鏈表的數據結構為:
struct ListNode { int val; struct ListNode *next; ListNode(int x) :val(x), next(NULL) {} };
題目一:從尾到頭打印鏈表:輸入一個鏈表。從尾到頭打印鏈表每一個節點的值。
分析:
難點在於鏈表僅僅有指向後繼的指針,沒有指向前驅的指針。
轉換思路。結合棧後進先出的特點,能夠遍歷鏈表,依次將數據元素存入棧中,然後再依次出棧,即為從尾到頭的順序。
vector<int> printListFromTailToHead(struct ListNode* head) { ListNode *p=head; stack<int> temp; while(p) { temp.push(p->val); p=p->next; } vector<int>result; while(!temp.empty()) { result.push_back(temp.top()); temp.pop(); } return result; }
題目二:鏈表中倒數第k個結點:輸入一個鏈表,輸出該鏈表中倒數第k個結點。
分析:
(1)依據上題的啟示。事實上這個題也能夠借助棧來完畢。先從頭到尾依次將結點存入棧。然後取出從棧頂開始的第k個結點就可以。
(2)還有一種方法是使用先後指針來完畢,一個指針先從頭開始向前走k-1步,然後還有一個指針從頭開始走。當第一個指針指向最後一個 結點時,後一個指針指向倒數第k個結點。
邊界條件:要記得考慮k大於鏈表長度的情況和k=0的情況都返回空。
方法一:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) { ListNode *p=pListHead; stack<ListNode*> temp; int len=0; while(p) { ++len; temp.push(p); p=p->next; } if(len<k||k==0) return NULL; while(k!=1) { temp.pop(); --k; } return temp.top(); }
方法二:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) { if(pListHead==NULL||k==0) return NULL; ListNode *pAhead=pListHead; ListNode *pBehind=pListHead; for(int i=0;i<k-1;i++) { if(pAhead->next!=NULL) pAhead=pAhead->next; else return NULL; } while(pAhead->next!=NULL) { pAhead=pAhead->next; pBehind=pBehind->next; } return pBehind; }
題目三:反轉鏈表(鏈表逆序):輸入一個鏈表,反轉鏈表後,輸出鏈表的全部元素。
ListNode* ReverseList(ListNode* pHead) { if(!pHead) return pHead; ListNode *reverse=NULL; ListNode *pre=NULL; ListNode *next=NULL; ListNode *curr=pHead; while(curr) { next=curr->next; if(!next) reverse=curr; curr->next=pre; pre=curr; curr=next; } return reverse; }
題目四:合並兩個排序的鏈表:輸入兩個單調遞增的鏈表。輸出兩個鏈表合成後的鏈表,當然我們須要合成後的鏈表滿足單調不減規則。
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) { ListNode *result=new ListNode(0); ListNode *r=result; while(pHead1&&pHead2) { if(pHead1->val<=pHead2->val) { r->next=pHead1; r=r->next; pHead1=pHead1->next; } else { r->next=pHead2; r=r->next; pHead2=pHead2->next; } } if(pHead1) r->next=pHead1; if(pHead2) r->next=pHead2; return result->next; }
題目五:兩個鏈表的第一個公共結點,輸入兩個鏈表,找出它們的第一個公共結點。
分析:兩個鏈表都是單向鏈表。假設他們有公共的結點,那麽這兩個鏈表從某一結點開始,他們的next都指向同一個結點,之後全部的點都重合。不可能再出現分叉。所以它們的拓撲形看起來像一個Y形,而不可能是X形。
方法一:首先遍歷兩個鏈表得到它們的長度,就能知道哪個鏈表長,以及長的鏈表比短的鏈表多幾個結點。在第二次遍歷的時候,在較長的鏈表上先走相差的步數,接著同一時候在兩個鏈表上遍歷。找到的第一個同樣的結點就是它們的公共結點。
時間復雜度O(m+n)。不須要輔助棧。
方法二:分別將兩個鏈表存入兩個輔助棧中,然後比較兩個棧頂的結點是否同樣。假設同樣,則把棧頂彈出,接著比較下一個棧頂,直到找到最後一個同樣的結點。
時間復雜度O(m+n),空間復雜度O(m+n)。
方法一:
ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2) { int len1=0,len2=0; ListNode *p=pHead1,*q=pHead2; while(p) { len1++; p=p->next; } while(q) { len2++; q=q->next; } if(len1==0||len2==0) return NULL; p=pHead1;q=pHead2; while(len1>len2) { p=p->next; len1--; } while(len1<len2) { q=q->next; len2--; } while(p!=q) { p=p->next; q=q->next; } return p; }
ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2) { ListNode *p=pHead1; ListNode *q=pHead2; stack<ListNode *> temp1; stack<ListNode *> temp2; while(p) { temp1.push(p); p=p->next; } while(q) { temp2.push(q); q=q->next; } ListNode *result=NULL; while(!temp1.empty()&&!temp2.empty()&&temp1.top()==temp2.top()) { result=temp1.top(); temp1.pop(); temp2.pop(); } return result; }
題目六:鏈表中環的入口結點:一個鏈表中包括環,請找出該鏈表的環的入口結點。
分析:有兩個能夠面試的問題:一個題是推斷一個鏈表中。是否有環。
第二個是環的入口結點。
經典方法就是使用快慢指針。快的一次走兩步,慢的一次走一步,假設指針重合,說明鏈表有環。
在此基礎上,能夠想到,快的比慢的剛好多走了一個環的長度。並且速度是慢的二倍,說明快的總共走的是兩個環的長度。慢的總共走了一個環的長度。
所以保持慢指針如今的位置,讓快指針再次從頭走起。每次走一步,當這次兩個指針重合的時候。它們剛好都在環的入口結點上。
推斷是否有環的代碼:
bool HasLoop(ListNode* pHead) { ListNode *slow=pHead,*fast=pHead; while(fast&&fast->next) { slow=slow->next; fast=fast->next->next; if(slow==fast) return true; } return false; }
ListNode* EntryNodeOfLoop(ListNode* pHead) { ListNode *slow=pHead,*fast=pHead; while(fast&&fast->next) { slow=slow->next; fast=fast->next->next; if(slow==fast) { fast=pHead; while(fast!=slow) { fast=fast->next; slow=slow->next; } return slow; } } return NULL; }
轉載請註明出處:http://blog.csdn.net/xingyanxiao/article/details/47068509
劍指offer——鏈表相關問題總結