《剑指Offer》学习笔记--面试题13:在O(1)时间删除链表结点

来源:互联网 发布:设计模式php 编辑:程序博客网 时间:2024/05/01 09:39

题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。链表结点与函数的定义如下:

struct ListNode{int        m_nValue;ListNode*  m_pNext;};void DeleteNode(ListNode **pListHead, ListNode *pToBeDeleted);
在单向链表中删除一个结点,最常规的做法无疑是从链表的头结点开始,顺序遍历查找要删除的结点,并在链表中删除该结点。

之所以需要从头开始查找,是因为我们需要得到将被删除的结点的前面一个结点。在单向链表中,结点中没有指向前一个结点的指针,所以只好从链表的头结点开始顺序查找。

但是,如果我们把下一个结点的内容复制到需要删除的结点上覆盖原有的内容,再把下一个结点删除,那是不是就相当于把这个结点删除了?

上述思路还有一个问题:如果要删除的节点位于链表的尾部,那么它就没有下一个结点,怎么办?我们仍然从链表的头结点开始,顺序遍历得到该结点的前序结点,并完成删除操作。最后需要注意的是,如果链表中只有一个结点,而我们又要删除链表的头结点(也是尾结点),此时我们在删除结点之后,还需要把链表的头结点值为NULL。

代码如下:

void DeleteNode(ListNode **pListHead, ListNode *pToBeDeleted){if(!pListHead || !pToBeDeleted)return;//要删除的结点不是尾结点if(pToBeDeleted->m_pNext != NULL){ListNode* pNext = pToBeDeleted->m_pNext;pToBeDeleted->m_nValue = pNext->m_nValue;pToBeDeleted->m_pNext = pNext->m_pNext;delete pNext;pNext = NULL;}//链表只有一个结点,删除的是头结点也是尾结点else if(*pListHead == pToBeDeleted){delete pToBeDeleted;pToBeDeleted = NULL;*pListHead = NULL;}//链表中有多个结点,删除尾结点else{ListNode *pNode = *pListHead;while(pNode->m_pNext != NULL){pNode = pNode->m_pNext;}pNode->m_pNext = NULL;delete pToBeDeleted;pToBeDeleted = NULL;}}
接下来我们分析这种思路的时间复杂度。对于n-1个非尾结点而言,我们可以在O(1)的时间把下一个结点的内存复制覆盖要删除的节点,并删除下一个结点;对于尾结点而言,由于仍然需要顺序查找,时间复杂度是O(n)。因此总的平均时间复杂度是[(n-1)*O(1)+O(n)]/n,结果还是O(1),符合面试官的要求。

值得注意的是,上述代码仍然不是完美的代码,因为它基于一个假设:要删除的结点的确在链表中。我们需要O(n)的时间才能判断链表中是否包含某一结点。受到O(1)时间的限制,我们不得不把确保结点在链表中的责任推给了函数的调用者。在面试的时候,我们可以和面试官讨论这个假设,这样面试官就会觉得我们考虑问题非常全面。



0 0