题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。
函数的声明如下:
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)。
#include <iostream>
using namespace std;
typedef struct LNode{
int data;
LNode *next;
}LNode, *List;
void InsertList(List &l, int data)//头插入节点
{
List head;
LNode *p=new LNode;
p->data=data;
if(NULL==l)
p->next=NULL;
else
p->next=l;
l=p;
}
void PrintList(List l)//打印链表
{
LNode *p=l;
while(p)
{
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
void DeleteNode(List &l, LNode *toBeDeleted)//删除链表l中的toBeDeleted节点
{
LNode *p;
if(!l || !toBeDeleted)
return;
if(l==toBeDeleted)//若删除的节点时表头
{
p=l->next;
delete toBeDeleted ;
l=p;
}
else
{
if(toBeDeleted->next==NULL)//若删除的节点时最后一个节点
{
p=l;
while(p->next!=toBeDeleted)
p=p->next;
p->next=NULL;
delete toBeDeleted;
}
else//删除节点时中间节点
{
p=toBeDeleted->next;
toBeDeleted->data=p->data;
toBeDeleted->next=p->next;
delete p;
}
}
}
void main()
{
List l=NULL;
LNode *p;
int n;
InsertList(l, 3);
InsertList(l, 7);
InsertList(l, 12);
InsertList(l, 56);
InsertList(l, 33);
InsertList(l, 78);
InsertList(l, 20);
InsertList(l, 89);
PrintList(l);
cout<<"请输入要删除的节点:";
cin>>n;
p=l;
while(p->data!=n && p)
p=p->next;
if(!p)
{
cout<<"不存在这样的节点!"<<endl;
return;
}
else
DeleteNode(l, p);
cout<<"删除后的链表:";
PrintList(l);
}