18 刪除鏈表的節點/重復節點
題目描述:
題目一:在O(1)時間內刪除鏈表節點 :在給定的單向鏈表的頭指針和一個節點指針,定義一個函數在O(1)時間內刪除該節點。
//鏈表定義 struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } };
註意:輸入提供了要刪除節點的指針!!
測試用例:
1)功能測試(從有多個節點的鏈表中刪除中間、頭、尾節點;從只有一個節點的鏈表中刪除唯一的節點)
2)特殊輸入測試(頭指針為nullptr;指向要刪除的節點的指針為nullptr
解題思路:
1)常規做法: 平均時間復雜度為O(n) 面試時不建議使用
遍歷到要刪除節點的前一個節點pre,將要刪除節點的下一個節點賦值給前一個節點。pre->next = pCurrent->next;
2)不訪問當前節點的前一個節點:
因為要刪除節點的指針知曉,則其下一個節點是可以訪問的。將下一個節點的值(val和next)賦值給當前節點,然後刪除下一個節點即可。
pCurrent->val = pNext->val;
pCurrent->next = pNext->next;
delete pNext;
pNext = nullptr;
題目描述:
題目二:刪除鏈表中重復的節點
在一個排序的鏈表中,存在重復的結點,請刪除該鏈表中重復的結點,重復的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理後為 1->2->5
註:雖然題目示例給的重復節點的個數都是2,但是題目中並沒有明確指出。因此要考慮重復節點為多個的情況。如1->2->3->3->3->4處理後應該是1->2->4
測試用例:
1)功能測試(重復節點位於鏈表的頭部、中間、尾部;鏈表中沒有重復的節點)
2)特殊輸入測試(指向鏈表頭節點的指針為nullptr;鏈表中所有節點都是重復的
解題思路:
1)由於鏈表是排序的,只要考慮相鄰元素值是否相等,相等即重復。
如果當前節點與下一個節點值相同,他們是重復的節點,需要被刪除。為了保證刪除之後鏈表仍然是相連的,我們要把當前節點的前一個節點(因此要定義pPreNode)與後面值比當前節點值大的節點相連。
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } }; */ class Solution { public: ListNode* deleteDuplication(ListNode* pHead) { //鏈表為空時 if(pHead==nullptr)return pHead; ListNode* pPreNode = nullptr; //先初始為空 ListNode* pCurrentNode = pHead;//指向第一個節點 ListNode* pNext = nullptr; while(pCurrentNode!=nullptr){ //pCurrentNode->next!=nullptr // ListNode* pNext = pCurrentNode->next; //pNext可能為空 //不要放到循環裏面定義,每次循環都會定義 pNext = pCurrentNode->next; //pNext可能為空 if(pNext!=nullptr && pCurrentNode->val==pNext->val){ //pNext不為空時,才可以訪問其val值,即pNext->val存在 //有重復節點 //刪除重復節點 int value = pCurrentNode->val; ListNode* pNodeToDel = pCurrentNode; while(pNodeToDel!=nullptr && pNodeToDel->val==value){ //循環,以遍歷多個連續的重復值 pNext = pNodeToDel->next; // pNodeToDel會被刪除,要記錄一下它的下一個節點,否則之後就訪問不到了 delete pNodeToDel; pNodeToDel = nullptr; pNodeToDel = pNext; //移動到下一個節點 } //重新連接鏈表 if(pPreNode == nullptr){ //說明時頭節點 pHead = pNext; }else{ //非頭節點 pPreNode->next = pNext; //not pPreNode = pNext; 由於寫錯,總報段錯誤!!! 不能把pPreNode直接指向pNext } pCurrentNode = pNext; //別忘了也要更新 pCurrentNode }else{ //沒有重復節點 pPreNode = pCurrentNode; //pCurrentNode = pNext; pCurrentNode = pCurrentNode->next; } //無論是否刪除節點,每一次都要更新pPreNode與pCurrentNode,pNext會在每次循環初被更新 } return pHead; } };
註意:
[1] delete指針後一定先將指針置空。因為不確定後面是否會在使用指針(會賦值),如果不置空,使用未定義的指針是十分危險的行為。
[2] 要區分刪除的節點是否是頭節點:line36和line38。因為連接的操作是不同的
2)為了統一刪除節點後,頭節點與其他節點的連接關系。創建一個新的指針指向頭指針。
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } }; */ class Solution { public: ListNode* deleteDuplication(ListNode* pHead) { if(pHead==nullptr || pHead->next==nullptr) return pHead; ListNode* newpHead =new ListNode(-1); newpHead->next = pHead; ListNode* pPreNode = newpHead; ListNode* pNode = pHead; ListNode* pNext = nullptr; while(pNode!=nullptr){ pNext = pNode->next; if(pNext!=nullptr && pNode->val==pNext->val){ //有重復節點 ListNode* pToDel = pNode; int value = pNode->val; while(pToDel!=nullptr && pToDel->val==value){ pNext = pToDel->next; delete pToDel; pToDel = nullptr; pToDel = pNext; } pPreNode->next = pNext; pNode = pNext; //勿忘 }else{ //沒有重復節點 pPreNode = pNode; pNode = pNext; } } return newpHead->next; } };
3)使用遞歸方法:
當頭節點重復時,直接移動頭節點到第一個不重復的節點。頭節點不重復時,移動到下一個節點,剩余部分遞歸調用。這樣鏈表可以只處理頭節點重復的情況。
/* struct ListNode { int val; struct ListNode *next; ListNode(int x) : val(x), next(NULL) { } }; */ class Solution { public: ListNode* deleteDuplication(ListNode* pHead) { //遞歸的終止條件 if(pHead==nullptr || pHead->next==nullptr) return pHead; ListNode* pNode = nullptr; if(pHead->val==pHead->next->val){ //鏈表的頭節點有重復 int value = pHead->val; while(pHead!=nullptr && pHead->val==value){ pNode = pHead->next; delete pHead; pHead=nullptr; pHead=pNode; } return deleteDuplication(pHead); //因為輸入的是頭節點,沒有next指針接收,因此直接return }else{ //鏈表不重復 pHead->next = deleteDuplication(pHead->next); //返回的鏈表應該連接在頭指針的下面 } return pHead; } };
18 刪除鏈表的節點/重復節點