前幾天看到一個比較老的面試題,想了想還是總結一下記錄下來吧,
首先我先定義一下了鏈表的節點,後面的操作都通用這個類型的節點
struct pNode{
int p_mvalue;
pNode* p_next;
};
一:題目大意是:給兩個鏈表,找出是否相交:
已知的鏈表的頭結點 Head1, Head2
首先說第一種方法,很容易想到,就是兩個鏈表分別從頭到尾跑一遍,再看兩個節點的指針是否相同,簡單直接,但是不能直接返回交點節點的位置;
pNode* tmp1= Head1;
pNode* tmp2= Head2;
while(tmp1->p_next !=NULL )
tmp1= tmp1->p_next;
while(tmp2->p_next !=NULL )
tmp2= tmp2->p_next;
if(tmp1 == tmp2)
cout<<"相交"<<endl;</span>
解法二:就是利用棧,不過這個要求數據比較小,開兩個棧分別存放兩個鏈表的節點,首先比兩個棧中的頂端節點是否相同,如果相同,然後再逐個彈出,當頂端節點不相同時,那麼前一個彈出的節點就是該鏈表的交點。
stack< pNode* > p1;
stack< pNode* > p2;
pNode* tmp1= Head1;
pNode* tmp2= Head2;
while(tmp1){ //鏈表一入棧
p1.push(tmp1);
tmp1= tmp1->p_next;
}
while(tmp2 ){//鏈表二入棧
p2.push(tmp2);
tmp2= tmp2->p_next;
}
if(p1.top() == p2.top()){ //查找交點
while(!p1.empty() && !p2.empty()){
if(p1.top() == p2.top()){
cout<< "相交"<<endl;
break;
}
p1.pop();
p2.pop();
}
}
方法三:這是在知道兩個鏈表的長度的情況下,假設長度分別爲 len1, len2 ,再假設len1 <= len2, 那麼我們用兩個指針tmp1, tmp2 分別指向 Head1, Head2 並且讓 tmp2先走(len2 - len1)格,再兩個指針同時往後走,直到第一次 tmp == tmp2 或者其中一個指針爲 NULL,在這樣的情況下,如果鏈表相交的話,當遇到交點時便利就會結束,效率還是比較可觀;
pNode* tmp1= Head1;
pNode* tmp2= Head2;
int dif=len2- len1;
while(dif-- && tmp2 ){
tmp2= tmp2->p_next;
}
while(tmp1 && tmp2){
if(tmp1 == tmp2 ){
cout<< "相交"<<endl;
break;
}
tmp1= tmp1->p_next;
tmp2= tmp2->p_next;
}<
二: 判斷單鏈表中是否有環,很簡單,給出兩個指針,一個每次跑一格,另一個每次跑兩格,如果有環那麼一定會相遇的,如果其中一個指針爲NULL了 說明跑到表尾了 ,一定沒有環的,
三:刪除鏈表中的節點,大意爲給定單鏈表表的頭結點Head1 和一個指向鏈表中一個節點的指針p,刪除p所指向的節點。
或許你會想這個簡單,一分鐘不到就會寫下如下代碼:
void deletNodep(pNode* Head , pNode* p){
if( Head == NULL || p== NULL )
throw new std::exception("Invalid parameters");
pNode* ptmp=Head;
while( ptmp->p_next != p)
ptmp= ptmp->p_next;
pNode* q=ptmp->p_next;
ptmp->p_next = q->p_next;
delete(q);
}
仔細看看沒問題啊,還判斷了輸入的有效性,但是再想想o(n)的效率還可以接受,那你就錯了,其實是有o(1)的解法的:
void deletNodep(pNode* Head , pNode* p){
if( Head == NULL || p== NULL )
throw new std::exception("Invalid parameters");
pNode* q;
if( p->p_next !=NULL ){
q= p->p_next;
p->p_mvalue = p->p_next->p_mvalue;
p->p_next = p->p_next->p_next;
}else{
pNode* ptmp=Head;
while( ptmp->p_next != p)
ptmp= ptmp->p_next;
pNode* q=ptmp->p_next;
ptmp->p_next = q->p_next;
}
delete(q);
}
在輸入合法的情況下,如果該節點不是鏈表的尾節點,那麼就將該節點的下一個節點的值付給該節點,把該節點的下一節點刪除,時間複雜度 o(1),如果是尾節點,那沒辦法只有遍歷一遍了找到它前面那一節點的next域,再算平均效率 還是o(1),這次便可以接受了。
三、鏈表的輸出操作,順序輸出就不用說了吧,那說說逆序輸出吧。
假設鏈表是沒有頭結點,首先或許你會想到,我們可以下先把鏈表反轉了,在順序輸出,就方便多了,但是這是最不好的解法,翻轉的耗費不說,你改變了鏈表原有的順序,
那怎麼辦那,很快你會想到用棧,正好棧的性質是先進後出,先把所有節點都入棧再逐個彈出,代碼也蠻簡單:
void printList(pNode *pHead){
if(pHead == NULL )
return ;
stack< pNode* >pStack;
pNode* ptmp=pHead;
while(ptmp){
pStack.push(ptmp);
ptmp= ptmp->p_next;
}
while(!pStack.empty()){
pNode* p;
p=pStack.top();
cout<< p->p_mvalue <<endl;
pStack.pop();
}
}
不過既然想到了棧,那爲什麼不用遞歸呢,那樣的話代碼更加簡潔,遞歸計算基於棧的啊,所以更好的方法應該是這樣的:
void printList(pNode *pHead){
if(pHead == NULL )
return ;
printList(pHead->p_next );
cout<< pHead->p_mvalue << endl;
}
哈哈是不是簡單多了
四、單鏈表翻轉,
就是已知一單鏈表的第一個節點的位置,將該鏈表翻轉,並返回翻轉後鏈表的第一個節點的指針
實現這個我們需要藉助幾個輔助指針依次爲,自己拿張紙劃一劃就知道了是思路很清晰,代碼如下:
pNode * revList(pNode *pHead){
if(pHead == NULL )
return NULL;
pNode *p=NULL;
pNode *q=pHead;
pNode *revHead= NULL;
while(q!=NULL){
pNode *r= q->p_next;
q->p_next=p;
if(r== NULL)
revHead= q;
p= q;
q= r;
}
return revHead;
}
還有一寫問題,如鏈表的合併啊,雙向鏈表的操作等等,感覺都不是特別的難,找張紙畫畫,思路想清楚了,代碼都很好實現的。