1. 程式人生 > >18 刪除鏈表的節點/重復節點

18 刪除鏈表的節點/重復節點

value pre 次循環 fff 表頭 節點 需要 調用 not

題目描述:

題目一:在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 刪除鏈表的節點/重復節點