鏈表數據結構操做靈活,常常出如今各大公司的面試題中,所以,本文總結了常見的有關鏈表的面試題。node
首先定義鏈表的數據結構:面試
template<typename T> struct Node { T data; Node* next; };
1.基本操做算法
雖然將其命名成「基本操做」,由於這些對鏈表的操做與咱們正常接觸的對鏈表的操做,完成的功能是同樣的,如刪除一個節點,增長一個節點。但與正常操做不同的地方是,增長了一些限制條件。數據結構
1)刪除節點:只給定單鏈表中某個結點p(並不是最後一個結點,即p->next!=NULL)指針,刪除該結點函數
由於不知道頭節點的位置,所以,沒法遍歷得到節點p的前一個節點指針,採用傳統的刪除手法,將會形成鏈表在p出斷開。爲此,咱們採用「假裝」的技術:將欲刪除的節點的下一個節點NextNode中的數據copy到,並刪除NextNode節點。oop
template <typename T> bool delete_node(Node<T>* pNode) { Node<T> pNext = pNode->next; if (!pNext) { return false; } //copy pNext所指節點中的數據 pNode->data = pNext->data; pNode->next = pNext->next; delete pNext; return true; }
2)插入節點:只給定單鏈表中某個結點p(非空結點),在p前面插入一個結點ui
相似與以前刪除節點同樣的問題,咱們依然採用「偷樑換柱」技術:將欲插入的節點數據和節點p互換數據,而後,將新插入節點Next指向p的Next,p的Next指向插入的新節點。spa
template<typename T> void add_node(Node<T>* pNode, Node<T>* pNewNode) { //互換pNode與pNewNode內的數據 T data = pNode->data; pNode->data = pNewNode->data; pNewNode->data = data; //將互換數據後的節點連接起來 pNewNode->next = pNode->next; pNode->next = pNewNode; }
3)查找:查找鏈表中倒數第K個節點指針
通常的方法須要遍歷兩次:第一次就成鏈表的長度,第二次,遍歷計數到遍歷到第length-k個節點是結束。如何只在遍歷一次的時候,就能找出倒數第K個節點?可使用雙指針技術:1)首先兩個指針均從頭指針開始,第一個節點先走K個位置;2)兩個指針同步移動,當第一個指針指向尾節點是,第二個指針位置,即是須要求的節點位置:code
template<typename T> Node<T>* find_last_k(Node<T>* pHead, int k) {
if (pHead->next == NULL)
return NULL;
Node<T>* pFirst = pHead->next; Node<T>* pSeconde = pHead->next; for (int i = 1; i < k; i++, pFirst = pFirst->next); while (pFirst->next) { pFirst = pFirst->next; pSeconde = pSeconde->next; } return pSeconde; }
4)鏈表就地轉折
這個操做沒有涉及到多少技巧,只須要主要操做順序:
template<typename T> Node<T>* reverse(Node<T>* pHead) { Node<T>* pReverseHead = new Node<T>(); pReverseHead->next = NULL; Node<T>* pCurNode = pHead->next; Node<T>* pNext = NULL; while (pCurNode) { pNext = pCurNode->next; pCurNode->next = pReverseHead->next; pReverseHead->next = pCurNode; pCurNode = pNext; } return pReverseHead; }
2.技巧題
1)判斷鏈表是否相交
判斷鏈表相交有兩個功能需求:僅判斷鏈表是否相交;給出鏈表相交的第一個節點。第一個功能相對簡單,如何兩個鏈表相交,那麼兩個鏈表最後一個節點必然是相同的,實現代碼以下:
template <typename T> bool is_intersected(Node<T>* pAHead, Node<T>* pBHead) { if (pAHead != NULL || pBHead != NULL) { return false; } else { Node<T>* pANode = pAHead; Node<T>* pBNode = pBHead; while (pANode->next) pANode = pANode->next; while (pBNode->next) pBNode = pBNode->next; return pANode == pBNode ? true : false; } }
第二個功能,求兩個相交的那個節點。算法首先求出兩個兩邊的長度之差|lenA-lenB|,讓較長鏈表走|lenA-lenB|個步長;而後讓這兩個鏈表同時開始移動,並判斷當前節點的指針是否指向同一節點template <typename T>
int list_len(Node<T>* pAHead) { Node<T>* pNode = pHead->next; int listLen = 0; while (pNode)
{
pNode = pNode->next; listLen++;
} return listLen; } template <typename T> Node<T>* find_intersect_node(Node<T>* pAHead, Node<T>* pBHead) { Node<T>* intersectNode = NULL; if (pAHead == NULL || pBHead == NULL) { return intersectNode; } int lenA = list_len(pAHead); int lenB = list_len(pBHead); int longerLen = 0; Node<T>* pShort = NULL; Node<T>* pLong = NULL; if (lenA > lenB) { longerLen = lenA - lenB; pLong = pAHead->next; pShort = pBHead->next; } else { longerLen = lenB - lenA; pLong = pBHead->next; pShort = pAHead->next; } while (longerLen-- > 0) pLong = pLong->next; while (pLong) { if (pLong == pShort) { intersectNode = pLong; break; }
pLong = pLong->next;
pShort = pShort->next;
}
return intersectNode; }
2)判斷鏈表是否由環
在判斷鏈表是否相交的一個前提條件是,鏈表不存在環。若是鏈表存在環,遍歷這個鏈表將會是個死循環。那麼如何判斷一個鏈表是否存在一個環?第一種方法,記錄鏈表中給每一個節點的地址,當出現了兩個相同的地址時,能夠判斷兩個鏈表是相交的。
第二種方法,是採用快慢指針法,慢指針p每走一次步長一個節點,快指針q每走一次步長兩個節點,當着兩個指針相遇時,則鏈表存在環。算法的證實以下:
如圖所示當p在環的入口節點時,q已經進入環中,令q=p-q, p=0,環的長度爲m,當慢指針走過i次步長後,p與q相遇,須要則知足
(q+2*i) % m = i % m
上式成立的條件是:
q + 2*i = i + k * m(k爲整數)
q + i = k*m
m與q是常量,而i和k是變量,上面的表達式必定存在某個(i, k)使表達式成立。
判斷鏈表是否相交的代碼以下:
/************************************************************************/ /* 函數功能: 判斷鏈表是否有環 * 輸入參數: pHead,指向鏈表的頭指針 * 返回參數: 返回快慢指針相遇的節點指針, 若是沒有換返回NULL /************************************************************************/ template <typename T> Node<T>* is_exist_loop(Node<T>* pHead) { Node<T>* pNode = NULL; if (pHead ==NULL) { return pNode } Node<T>* p = pHead; Node<T>* q = pHead; while (q->next && q->next->next) { q = q->next->next; p = p->next; if (p==q) { pNode = p; break; } } return pNode; }
那麼如何找到環的入口節點呢?
假設在快慢指針相遇的節點pNode處「斷開」(很是物理意義上的斷開),那麼將存在兩天邏輯上的鏈表:以pHead爲頭指針pNode爲結尾的鏈表A,以pNode爲首節點,並以pNode爲尾節點的兩個鏈表,這兩個鏈表相交與環的入口節點。
/************************************************************************/ /* 函數功能: 尋找有環鏈表的入口節點 * 輸入參數: pHead,指向鏈表的頭指針,pCrossNode快慢指針相遇的節點 * 返回參數: 鏈表的入口節點 /************************************************************************/ template<typename T> Node<T>* find_enter_node(Node<T>* pHead, Node<T>* pCrossNode) {
int lenA=0, lenB=1; //鏈表B從對首節點開始計算,len起始於1
for (Node<T>* pNode=pHead->next ; pNode != pCrossNode; pNode = pNode->next) //計算鏈表A的長度
lenA++;
for (Node<T>* pNode=pCrossNode->next; pNode!=pCrossNode; pNode = pNode->next)//計算鏈表B的長度
lenB++;
Node<T>* pLong = NULL; Node<T>* pShort = NULL; int longLen = 0; if (lenA > lenB) { pLong = pHead->next; pShort = pCrossNode; longLen = lenA - lenB; } else { pLong = pCrossNode; pShort = pHead->next; longLen = lenB - lenA; } while (longLen-- > 0) { pLong = pLong->next; } Node<T>* pEnterNode = NULL; while (pLong) { if (pLong == pShort) { pEnterNode = pLong; break; } pLong = pLong->next; pShort = pShort->next; } return pEnterNode; }
程序的主函數以下:
int main() { int myArray[] = {0, 1, 2, 3, 4, 5}; Node<int>* pHead = build_list(myArray, 6); Node<int>* pNode = pHead->next; while(pNode && pNode->data != 3) pNode = pNode->next; cout<<"Before Delete Element 3:"; print(pHead); cout<<"After Delete Element 3:"; delete_node(pNode); print(pHead); pNode = pHead->next; while(pNode && pNode->data != 4) pNode = pNode->next; Node<int>* pAddNode = new Node<int>(6); cout<<"After Insert Element 6 before element 4:"; add_node(pNode, pAddNode); print(pHead); pNode = find_last_k(pHead, 4); cout<<"The last 4 element:"<<pNode->data<<endl; cout<<"Reverse list:"; pHead = reverse(pHead); print(pHead); //Build list B via add list A; int myArrayB[] = {7, 8, 9}; Node<int>* pHeadB = build_list(myArrayB, 3); Node<int>* pBNode = pHeadB->next; while (pBNode->next) pBNode = pBNode->next; pBNode->next = pNode; cout<<"List A:"; print(pHead); cout<<"List B:"; print(pHeadB); Node<int>* pIntersetNode = find_intersect_node(pHead, pHeadB); cout<<"The intersect element is:"<<pIntersetNode->data<<endl; //Build cycle via list A at element 4 Node<int>* pLast3 = find_last_k(pHead, 3); Node<int>* pLast1 = find_last_k(pHead,1); pLast1->next = pLast3; // build a loop pNode = is_exist_loop(pHead); Node<int>* pEnterNode = find_enter_node(pHead, pNode); if (pNode) { cout<<"Exist loop, The enter node is:"<<pEnterNode->data<<endl; } else cout<<"Not Exist a loop"<<endl; system("pause"); return 0; }
運行結果以下: