gavanwanggw

导航

[经典面试题]在O(1)时间删除链表结点

【题目】

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

struct ListNode

{

    int        value;

    struct ListNode*  next;

};

函数的声明例如以下:

void DeleteNode(ListNode* head,ListNode* node);

【思路】

这是一道广为流传的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)。

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


/*********************************
*   日期:2014-10-29
*   作者:SJF0115
*   题目: 给定链表的头指针和一个结点指针,在O(1)时间删除该结点
*   来源:经典面试题
*   总结:
**********************************/
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};
//O(1)时间 删除链表中的节点
void DeleteNode(ListNode** head,ListNode* node){
    if(head == NULL || node == NULL){
        return;
    }
    ListNode* p = node->next;
    // 删除的不是末尾节点 O(1)
    if(p != NULL){
        //删除p节点
        node->next = p->next;
        // 节点p拷贝到node节点
        node->next = p->next;
        node->val = p->val;
        delete p;
        p = NULL;
    }
    // 删除的是末尾节点 O(n)
    else{
        ListNode* pre = *head;
        // 末尾节点
        while(pre->next != node){
            pre = pre->next;
        }
        //删除node节点
        pre->next = NULL;
        delete node;
        node = NULL;
    }
}

int main() {
    ListNode* node1 = new ListNode(1);
    ListNode* node2 = new ListNode(2);
    ListNode* node3 = new ListNode(3);
    ListNode* node4 = new ListNode(4);
    ListNode* node5 = new ListNode(5);

    node1->next = node2;
    node2->next = node3;
    node3->next = node4;
    node4->next = node5;

    //无头结点链表
    DeleteNode(&node1,node2);

    while(node1 != NULL){
        printf("%d ",node1->val);
        node1 = node1->next;
    }
    return 0;
}



何海涛:

值得注意的是。为了让代码看起来简洁一些,上面的代码基于两个如果:(1)给定的结点的确在链表中。(2)给定的要删除的结点不是链表的头结点。不考虑第一个如果对代码的鲁棒性是有影响的。

至于第二个如果,当整个列表仅仅有一个结点时,代码会有问题。但这个如果不算非常过分。由于在有些链表的实现中。会创建一个虚拟的链表头,并非一个实际的链表结点。

这样要删除的结点就不可能是链表的头结点了。当然,在面试中,我们能够把这些如果和面试官交流。这样,面试官还是会认为我们考虑问题非常周到的。




posted on 2017-05-06 15:09  gavanwanggw  阅读(177)  评论(0编辑  收藏  举报