剑指offer--删除链表中的结点( 在O(1)时间内删除链表结点 + 删除有序链表中的所有重复结点 )

链表结点定义如下

typedef struct Node{
    
    
	int m_nValue;
	struct Node *m_pNext;
}ListNode;

题目一:删除有序链表中的所有重复结点

分析:从头开始遍历链表,两个指针,一个负责遍历(pNode),一个负责指向不重复的最后一个结点(pPreNode),如果遍历到的当前结点和他的下一个结点内容不同,则 pPreNode指向pNode,pNode继续向下走。如pNode和它的下一个结点内容相同,则定义一个删除指针 (pToBeDel) 等pNode ,删除 pToBeDel, 直到 pToBeDel 的内容为空或者其中的内容 不等于pNode的内容 。如果刚开始的头结点就开始重复 ,那么pPreNode自然为空 ,*pHead应该等于删除的最后一个重复结点的下一个 。否则pPreNode的下一个应该是删除的最后一个重复结点的下一个, pNode 自然移动到删除的最后一个重复结点的下一个。一直循环直到遍历结束 pNode为空。
在这里插入图片描述

void DeleteDuplication(ListNode **pHead){
    
       

	if(pHead  == NULL || *pHead == NULL)
		return;

	ListNode *pPreNode = NULL;  //指向最后一个不重复结点
	ListNode *pNode = *pHead;   //从头开始遍历

	while(pNode != NULL){
    
    
		ListNode *pNext = pNode ->m_pNext;
		bool needDelete = false;

		if(pNext != NULL && pNext->m_nValue == pNode->m_nValue)  //比较当前结点和下一个结点是否值一样
			needDelete = true;

		if(!needDelete){
    
      //不一样 则最后一个不重复结点等于当前结点
			pPreNode = pNode;
			pNode = pNode->m_pNext;
		}
		else
		{
    
    
			int nValue = pNode->m_nValue;                               // 1->2->3->3->4->4->5
			ListNode *pToBeDel = pNode;

			while(pToBeDel != NULL && pToBeDel->m_nValue == nValue){
    
    
				pNext = pToBeDel->m_pNext; 
				delete pToBeDel;

				pToBeDel = NULL;

				pToBeDel = pNext;
			}
			if(pPreNode  == NULL) //如果开始的头结点就是重复的,那么删除重复结点以后   头结点就应该是删除的最后一个重复结点的下一个结点
				*pHead = pNext;
			else
				pPreNode->m_pNext = pNext; //链表最后一个不重复的结点的下一个的应该是   删除的最后一个重复结点的下一个(比如删除3 3后 2的下一个应该是4)
			pNode = pNext;
		}
	}
}

题目二:O(1) 时间内删除链表的指定结点

分析:在单向链表中删除一个结点,常规的做法无疑是从链表的头结点开始,顺序遍历查找要删除的结点,并在链表中删除该结点。
我们想删除结点3,可以从链表的头结点1开始顺序遍历,发现结点2的m_pNext指向要删除的结点3,于是我们可以把结点2的m_pNext指向3的下一个结点,即结点4。指针调整之后,我们就可以安全地删除结点3并保证链表没有断开,这种思路由于需要顺序查找,时间复杂度自然就是O(n)了。
之所以需要从头开始查找,是因为我们需要得到将被删除的结点的前一个结点。在单向链表中,结点中没有指向前一个结点的指针,所以只好从链表的头结点开始顺序查找。

但是我们不一定必须要得到被删除的结点的前一个结点,通过需要被删除的结点可以很方便地得它的下一个结点。如果我们把下一个结点的内容复制到需要删除的结点上覆盖原有的内容,再把下一个结点删除,就相当于把当前需要删除的结点删除了。

上述思路还有一个问题:如果要删除的结点位于链表的尾部,那么它就没有下一个结点,怎么办?我们仍然从链表的头结点开始,顺序遍历得到该结点的前序结点,并完成删除操作。

最后需要注意的是,如果链表中只有一个结点,而我们又要删除链表的头结点(也是尾结点),那么,此时我们在删除结点之后,还需要把链表的头结点设置为NULL。

在这里插入图片描述


void DeleteNode(ListNode ** pListHead,ListNode *pToBeDel){
    
    
	if(!pListHead || !pToBeDel)
		return;

    //要删除的不是尾结点
	if(pToBeDel->m_pNext != NULL){
    
    
		ListNode*pNext = pToBeDel->m_pNext;

		pToBeDel->m_nValue = pNext->m_nValue;                // 1->2->3->4->5
		pToBeDel->m_pNext = pNext->m_pNext;

		delete pNext;
		pNext = NULL;
	}
   //链表只有一个结点 删除头结点 (也是尾结点)
	else if(*pListHead == pToBeDel){
    
    
		delete pToBeDel;
		pToBeDel = NULL;
		*pListHead = NULL;
	}

	//链表中有多个结点 删除 尾结点
	else
	{
    
    
		ListNode *pNode = *pListHead;
		while(pNode->m_pNext != pToBeDel){
    
    
			pNode = pNode->m_pNext;
		}
		pNode->m_pNext = NULL;
		delete pToBeDel;
		pToBeDel = NULL;
	}
}
		

接下来我们来分析时间复杂度:
对于n-1个非尾节点而言,我们可以在O(1)时间内把下一个节点的内存复制覆盖要删除的节点,并删除下一个节点;对于尾节点而言,由于仍然需要顺序查找,时间复杂度是O(n)。因此,总的平均时间复杂度是[(n-1)*O(1)+O
(n)]/n,结果还是O(1)。

Guess you like

Origin blog.csdn.net/scarificed/article/details/120377767