關於鏈表的考察
鏈表是面試里面經常涉及到的考點,因為鏈表的結構相比於Hashmap、Hashtable、Concurrenthashmap或者圖等數據結構簡單許多,對於后者更多面試的側重點在於其底層實現。比如Hashmap中Entry<k,v>等操作、如何擴容、容量的設定等。鏈表的考察更側重於代碼的書寫和思路的形成。雖然說,鏈表的結構簡單,但是涉及到指針的操作,容易引申出一些挑戰性的考題,其中也牽涉到諸多小的細節的考慮,更能看出代碼書寫的能力和功底。
面試題:反轉鏈表
題目:定義一個函數,輸入一個鏈表的頭結點,反轉該鏈表並輸出反轉后鏈表的頭結點。
下面給出了鏈表結點的定義:
1 struct ListNode { 2 int val; 3 struct ListNode *next; 4 ListNode(int x) : 5 val(x), next(NULL) { 6 } 7 };
分析:
鏈表前后元素的關聯就是通過指針實現的,每個鏈表都有一個next指針指向下一個結點,末尾的節點的next域則置NULL;
反轉鏈表就是要求修改指針的指向。下面的圖就是反轉前和反轉后的效果。
反轉前:
反轉后:
下面來談談如何對鏈表進行反轉。
假設我們現在正在對結點v進行反轉操作,即原來結點u的next域指向v(圖中已經調整完畢,現在指向前一個結點),v的next域指向w。現在要做的是將v的next域指向u。從圖中我們可以看出,當把v的next指針指向u的同時,原先指向的w就已經無法被正常的訪問到了,為了避免“斷鏈”,我們必須在指針更改指向之前,保存修改結點的下一結點。同時我們也必須存儲上一個結點,因為next域即將修改指向該結點。因此定義三個指針,分別指向當前遍歷的結點,前一個結點和后一個結點。
算法實現如下:
1 ListNode* ReverseList(ListNode* pHead) 2 { 3 ListNode* pReversedHead = NULL; 4 ListNode* pNode = pHead; 5 ListNode* pPrev = NULL; 6 while(pNode != NULL) 7 { 8 ListNode* pNext = pNode->m_pNext; 9 10 if(pNext == NULL) 11 pReversedHead = pNode; 12 13 pNode->m_pNext = pPrev; 14 15 pPrev = pNode; 16 pNode = pNext; 17 } 18 19 return pReversedHead; 20 }
當然,上面的源碼中用到了四個指針,看完源碼就會發現和上面分析的原理並沒有相悖。或者下面這樣也是可以的,兩者的思路一致,沒有差別。只不過下面的代碼必須注意一點,跳出while循環的時候,最后一個結點的next域別忘記指向前一個結點,否則就會導致“斷鏈”。
1 ListNode* ReverseList(ListNode* pHead) { 2 ListNode *root=pHead; 3 ListNode *pre=NULL; 4 ListNode *next=NULL; 5 if(pHead==NULL) return NULL; 6 while(root->next){ 7 next=root->next; 8 root->next=pre; 9 pre=root; 10 root=next; 11 } 12 root->next=pre; 13 return root; 14 }