微软之33 在O(1)的时间内删除链表的节点

转载自 July

转载自:July  http://blog.csdn.net/v_JULY_v/article/details/6126406

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

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

struct ListNode

{

    int        m_nKey;

    ListNode m_pNext;

};

函数的声明如下:                                

void DeleteNode(ListNode*pListHead,ListNode*pToBeDeleted);

分析:这是一道广为流传的Google面试题,能有效考察我们的编程基本功,还能考察我们的反应速度,更重要的是,还能考察我们对时间复杂度的理解。

在链表中删除一个结点,最常规的做法是从链表的头结点开始,顺序查找要删除的结点,找到之后再删除。由于需要顺序查找,时间复杂度自然就是O(n)了。

我们之所以需要从头结点开始查找要删除的结点,是因为我们需要得到要删除的结点的前面一个结点。我们试着换一种思路。我们可以从给定的结点得到它的下一个结点。这个时候我们实际删除的是它的下一个结点,由于我们已经得到实际删除的结点的前面一个结点,因此完全是可以实现的。当然,在删除之前,我们需要需要把给定的结点的下一个结点的数据拷贝到给定的结点中。此时,时间复杂度为O(1)

上面的思路还有一个问题:如果删除的结点位于链表的尾部,没有下一个结点,怎么办?我们仍然从链表的头结点开始,顺便遍历得到给定结点的前序结点,并完成删除操作。这个时候时间复杂度是O(n)

那题目要求我们需要在O(1)时间完成删除操作,我们的算法是不是不符合要求?实际上,假设链表总共有n个结点,我们的算法在n-1总情况下时间复杂度是O(1),只有当给定的结点处于链表末尾的时候,时间复杂度为O(n)。那么平均时间复杂度[(n-1)*O(1)+O(n)]/n,仍然为O(1)

基于前面的分析,我们不难写出下面的代码。

参考代码:

///

//Delete a node in a list

//Input: pListHead - the head of list

//        pToBeDeleted - the node to be deleted

///

void DeleteNode(ListNode*pListHead,ListNode*pToBeDeleted)

{

    if(!pListHead || !pToBeDeleted)

       return;

 

    // if pToBeDeletedis not the last node in the list

    if(pToBeDeleted->m_pNext!=NULL)

    {

       // copy datafrom the node next to pToBeDeleted

       ListNode*pNext =pToBeDeleted->m_pNext;

       pToBeDeleted->m_nKey =pNext->m_nKey;

       pToBeDeleted->m_pNext =pNext->m_pNext;

 

       // delete thenode next to the pToBeDeleted

       delete pNext;

       pNext= NULL;

    }

    // if pToBeDeletedis the last node in the list

    else

    {

       // get the nodeprior to pToBeDeleted

       ListNode*pNode =pListHead;

       while(pNode->m_pNext!=pToBeDeleted)

       {

           pNode= pNode->m_pNext;        

       }

 

       // deletedpToBeDeleted

       pNode->m_pNext =NULL;

       delete pToBeDeleted;

       pToBeDeleted= NULL;

    }

} 

值得注意的是,为了让代码看起来简洁一些,上面的代码基于两个假设:(1)给定的结点的确在链表中;(2)给定的要删除的结点不是链表的头结点。不考虑第一个假设对代码的鲁棒性是有影响的。至于第二个假设,当整个列表只有一个结点时,代码会有问题。但这个假设不算很过分,因为在有些链表的实现中,会创建一个虚拟的链表头,并不是一个实际的链表结点。这样要删除的结点就不可能是链表的头结点了。当然,在面试中,我们可以把这些假设和面试官交流。这样,面试官还是会觉得我们考虑问题很周到的。

以下是我的代码:

typedef struct LNode
{
	int m_nKey;
	struct LNode* next;
}LNode,*LinkList;

void createList(LinkList &head)
{
	head=(LinkList)malloc(sizeof(LNode));
	head->m_nKey=0;
	head->next=NULL;
	int len=10;
	while(len)
	{
		LinkList temp=(LinkList)malloc(sizeof(LNode));
		temp->m_nKey=len;
		temp->next=head->next;
		head->next=temp;
		len--;
	}
}
void traverse(LinkList head)
{
	LinkList p=head->next;
	while(p)
	{
		cout<<p->m_nKey<<" ";
		p=p->next;
	}
	cout<<endl;
}
void Delete(LinkList p,LinkList head)
{
	if(!p||!head)
		return;
	LinkList q=p->next;
	if(q)
	{
		p->next=q->next;
		p->m_nKey=q->m_nKey;
		delete q;
		q=NULL;
	}
	else
	{
		LinkList temp=head->next;
		while(temp->next!=p)
		{
			temp=temp->next;
		}
		temp->next=p->next;
		delete p;
		p=NULL;
	}
}
int main()
{
	LinkList head=NULL;
	createList(head);
	traverse(head);
	Delete(head->next->next->next,head);
	traverse(head);
	
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值