程序员面试题精选100题(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 pToBeDeleted is not the last node in the list
      if(pToBeDeleted->m_pNext != NULL)
      {
            // copy data from the node next to pToBeDeleted
            ListNode* pNext = pToBeDeleted->m_pNext;
            pToBeDeleted->m_nKey = pNext->m_nKey;
            pToBeDeleted->m_pNext = pNext->m_pNext;
 
            // delete the node next to the pToBeDeleted
            delete pNext;
            pNext = NULL;
      }
      // if pToBeDeleted is the last node in the list
      else
      {
            // get the node prior to pToBeDeleted
            ListNode* pNode = pListHead;
            while(pNode->m_pNext != pToBeDeleted)
            {
                  pNode = pNode->m_pNext;            
            }
 
            // deleted pToBeDeleted
            pNode->m_pNext = NULL;
            delete pToBeDeleted;
            pToBeDeleted = NULL;
      }
} 


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

本文已经收录到《剑指Offer——名企面试官精讲典型编程题》一书中,有改动,书中的分析讲解更加详细。欢迎关注。

博主何海涛对本博客文章享有版权。网络转载请注明出处http://zhedahht.blog.163.com/。整理出版物请和作者联系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我来回复。7-1 数据结构实验之链表一:顺序建立链表是一道链表实验目,要求学生使用顺序存储的方式建立链表,并实现链表的基本操作,如插入、删除、查找等。这道目可以帮助学生深入理解链表的原理和实现方式,提高其对数据结构的理解和应用能力。 ### 回答2: 链表是一种常见的数据结构,它是由一系列结点组成的线性结构。结点包括两部分内容,一部分是存储数据的元素,另一部分是向下一个结点针。链表的优点在于可以动态地增加、删除结点,因此它常用于需要频繁增删数据的场合。 本次实验是链表的第一部分,即顺序建立链表。顺序建立链表的是从到尾依次输入结点,将其链接在一起形成链表。具体实现方法如下: 1. 定义一个结点结构体,包含存储数据的元素和向下一个结点针。 2. 在主函数中定义一个结点结点的数据元素可以不存储有效数据,仅用于方便遍历链表。 3. 通过循环输入数据,并将数据存储在新结点中。 4. 将新结点结点向的下一个结点,再将结点向新结点,即完成了新结点的插入。 5. 继续输入下一个数据,直到输入完毕。 6. 最后遍历链表输出所有数据。 顺序建立链表的算法简单易懂,适合初学者入门。但是该算法的缺点在于它需要依次遍历链表时间复杂度较高,因此在数据量较大时效率会比较低。此外,该算法只能按照输入的顺序依次插入数据,不能根据数据的大小或其他规则来插入新结点,因此在一些需要按照某种规则排序的场合可能无法满足需求。 总之,顺序建立链表链表的一种基本实现方法,掌握该算法对于学习其他链表操作是非常有帮助的。 ### 回答3: 链表数据结构中很重要的一种,它可以实现灵活的插入、删除、查找等操作。其中,顺序建立链表链表的一种常见方法。 顺序建立链表的过程中,我们需要先定义一个节点结构体,例如: typedef struct node{ int data; // 数据域 struct node* next; // 针域 }Node; 在这个结构体中,有一个数据域用来存储节点的值,还有一个针域用来向下一个节点。接下来,我们可以通过输入数据来构建链表。 具体实现方法是,先定义一个针,然后通过循环读取输入的数据,并将其插入到链表中。例如,以下是一个简单的顺序建立链表的代码: void CreateList(Node *&head){ int data; Node *p, *q; head = NULL; q = head; cin >> data; while(data != -1){ p = new Node; p->data = data; p->next = NULL; if(head == NULL){ head = p; q = head; } else{ q->next = p; q = p; } cin >> data; } } 这段代码中,首先定义一个针head,将其初始化为NULL。然后依次读取输入的数据,直到读取到-1为止。在每次读取数据之后,新建一个节点p,并将输入的数据存储在节点的数据域中,然后将p插入到链表尾部。 顺序建立链表的优点是简单快捷,代码实现比较容易,适用于已经知道链表数据的顺序的情况。但是其缺点是无法处理插入数据的顺序不确定的情况,因此需要使用其他方法进行插入操作,例如插法或尾插法。 综上所述,链表是一个十分重要的数据结构,顺序建立链表链表的一个常用构建方法,其优点是简单快捷,但对于插入顺序不确定的数据,需要使用其他方法进行插入操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值