O(1)时间删除链表结点

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

typedef struct ListNode
{
	int val;
	struct ListNode *p_next;
}NODE, *PNODE;
void delete_node(PNODE *pListHead, PNODE pToDeleted);

一般来说,我们拿到一个删除链表的某个结点的题目,最常规的解法无疑是从链表的头结点开始顺序遍历,依次查找要删除的结点,找到被删除结点的前一个结点后,就可以在链表中删除结点。就像下面这样:

假设要删除的结点是b,那就先找到b的前一个结点a,然后使a的下一个结点变为c,最后释放b。当然如果整个链表中只有一个结点为b结点,那么b就是头结点了,算是一种特殊情况。

显而易见,因为这个算法需要遍历整个链表,所以它的时间复杂度是O(n)。并不是我们想要的结果。那就只好另辟蹊径了。

图示如下:

这种思路的做法是,因为我们要删的结点是b,而且已经给了指向b结点的指针,那么我们可以在O(1)时间内直接找到的结点就是它了。找到了b结点,那么b的下一个结点c肯定也找到了,我们用c的值覆盖掉b,然后在删掉最后的c结点。这样不也相当于删除了结点b吗?而且时间复杂度自然是O(1)了。

这个思路还有一个问题,就是如果要删除的结点是尾结点怎么办,那它就没有下一个结点了,这种情况下我们只好采用第一种思路了。

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

下面就是这种思路的具体实现了:

void delete_node(PNODE *pListHead, PNODE pToDeleted)
{
	//假设前提:链表中存在pToDeleted这个结点
	if ((NULL == pListHead) || (NULL == *pListHead) || (NULL == pToDeleted))
		return;

	if ((*pListHead)->val == pToDeleted->val)										//待删结点是头指针
	{
		PNODE tmp = *pListHead;
		*pListHead = tmp->p_next;
		free(tmp);
		tmp = NULL;
	}
	else if (NULL != pToDeleted->p_next && (*pListHead)->val != pToDeleted->val)	//待删结点不是尾指针也不是头指针
	{
		PNODE next = pToDeleted->p_next;
		pToDeleted->val = next->val;
		pToDeleted->p_next = next->p_next;
		free(next);
		next = NULL;
	}
	else																			//是尾指针但不是头指针,需要遍历整个链表
	{
		PNODE tmp = *pListHead;

		while (tmp->p_next->val != pToDeleted->val)
		{
			tmp = tmp->p_next;
		}
		free(pToDeleted);
		pToDeleted = NULL;
		tmp->p_next = NULL;
	}
}

void print(PNODE head)
{
	while (NULL != head)
	{
		printf("%d ", head->val);
		head = head->p_next;
	}
	printf("\n");
}
int main()
{
	PNODE p1, p2, p3;
	PNODE head = NULL;

	p1 = (PNODE)malloc(sizeof(NODE));
	p2 = (PNODE)malloc(sizeof(NODE));
	p3 = (PNODE)malloc(sizeof(NODE));

	p1->val = 1;
	p2->val = 2;
	p3->val = 3;
	p1->p_next = p2;
	p2->p_next = p3;
	p3->p_next = NULL;

	head = p1;

	print(head);
	delete_node(&head, p1);
	print(head);
	delete_node(&head, p3);
	print(head);
	delete_node(&head, p2);

}

再来分析一下这个思路的时间复杂度,对(n-1)个非尾结点而言,时间复杂度为O(1);而对于尾结点而言,由于仍然需要遍历查找,所以时间复杂度是O(n)。

所以总的平均时间复杂度是[(n - 1) * O(1) + 1 * O(n)] / n ,结果仍然是O(1)。

还要注意的是,上边我们写的代码并不是完整的代码。仔细想想,我们就可以发现,这个程序是建立在要删除的结点一定在链表上存在的基础上。但是要判断一个1结点是否在链表上,需要O(n)的时间复杂度。所以保证链表上存在要删除的结点这一责任就抛给了这个函数的调用者。这是不得已而为之。

时间: 2024-07-29 21:23:10

O(1)时间删除链表结点的相关文章

面试题18(一):在O(1)时间删除链表结点

// 面试题18(一):在O(1)时间删除链表结点 // 题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该 // 结点.链表结点与函数的定义如下: // struct ListNode{ // int m_nValue; // ListNode* m_pNext; // }; // void deleteNode(ListNode** pListHead,ListNode* pToBeDeleted); 解题思路: 这是目前为止,唯一一道,我不看书就知道怎么做的题. 正

【Java】 剑指offer(17) 在O(1)时间删除链表结点

本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点. 思路 通常那样从头开始查找删除需要的时间为O(n),要在O(1)时间删除某结点,可以这样实现:设待删除结点i的下一个结点为j,把j的值复制到i,再把i的指针指向j的下一个结点,最后删除j,效果就相当于删除j. 注意特殊情况:1.当待删除结点i为尾结点时,无下一个结点,则只能从头到尾顺序遍历:2.当链

剑指Offer对答如流系列 - 在O(1)时间删除链表结点

面试题17:在O(1)时间删除链表结点 问题描述 给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点. 链表结构 public class Node{ int val; Node next; public Node(int value,Node next) { val=value; next=next; } } 问题分析 曾经未碰到这道题之前,删除链表的节点,用的方法非常原始.基础(代码如下),很明显这种原始的方式带来的时间复杂度为O(n) //从链表中删除index(0~

《剑指offer》第十八题:在O(1)时间删除链表结点

// 面试题18(一):在O(1)时间删除链表结点 // 题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该 // 结点. #include <cstdio> #include "List.h" void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted) { if (pListHead == nullptr || pToBeDeleted == nullptr) return; //多个

在O(1)时间删除链表结点

题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点. 链表结点与函数的定义如下: struct ListNode { int m_nValue; ListNode* m_pNext; }; void DeleteNode(ListNode** pListHead,ListNode* pToBeDeleted); 思路:我们可以很方便的得到要删除的结点的下一结点,如果我们把下一个结点的内容复制到需要删除的结点上覆盖原有的内容,再把下一个结点删除.就相当于把当前需要删除的

在O(1)时间删除链表结点——13

给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点. 因为要求时间复杂度为O(1),所以肯定不能遍历链表,那样的话时间复杂度就是O(N)了:可以想到,其实要求删除该结点,真正的目的并不是要将结点的数据包括结点所占的内存都给删除,只是想让数据消失就可以了,至于结点,除去任意一个结点所占的空间都是OK的: 所以,这里换一种思路,若要删除指定的结点,一般需要将前一个结点的next指针指向要删除结点的下一个结点,这样要删除的结点就可以脱离链表而被删除了,但这里关键就是即是单链表没有

33.在O(1)时间删除链表结点

http://zhedahht.blog.163.com/blog/static/254111742007112255248202/ 题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点.链表结点的定义如下: struct ListNode { int m_nKey; ListNode* m_pNext; }; 函数的声明如下: void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted); 分析:这是一道广为流传的Googl

在O(1)时间删除链表结点——剑指offer

题目:在给定单链表的头结点指针和一个结点指针,定义一个函数在O(1)时间删除该结点. 代码: 1 #include <stdio.h> 2 #include "malloc.h" 3 struct ListNode 4 { 5 int value; 6 ListNode *next; 7 }; 8 //尾插法创建链表 9 ListNode *createList() 10 { 11 ListNode *H = (ListNode *)malloc(sizeof(ListNo

在O(1)时间删除链表结点

给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点.链表结点与函数定义如下: struct ListNode { int m_nValue; ListNode* m_pNext; }; code: //把待删结点后面一个结点的值赋给待删结点,然后把待删结点next指针指向下下个结点,然后删除下个结点, 达到和删除待删结点一样的效果. void DeleteNode(ListNode** pListHead, ListNode* pToBeDeleted) { if (!pL