剑指Offer题目13:在O(1)时间删除链表结点(Java)

289 阅读2分钟

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

Basic

只要涉及到链表的增、删操作,就需要考虑以下三种场景:

场景1:(特殊场景)只有一个头结点,对头结点进行操作

场景2:(普通场景)有多个结点,操作中间节点

场景3:(特殊场景)对尾节点进行操作

解题思路

首先考虑普通场景,再考虑特殊场景。

普通场景:

由于要求的时间复杂度为O(1),如果采用常规思维,从头结点遍历到目标节点,则时间复杂度为O(n),无法满足要求。

删除某节点,意味着将某个目标值 date 对应的节点从链表中抹去,可以是直接删除,也可以是用新的值来覆盖旧的值,然后用
新的后序节点覆盖旧的后序节点。

如果采用直接删除的方式,需要从头结点遍历起,时间复杂度为 O(n), 无法满足 O(1) 的要求。

因此需要采取覆盖抹去目标节点的方式来达到删除节点的目的。

解决方案:

Step 1: 将目标节点的 next 节点的值赋给目标节点

Step 2:将目标节点的 next 的后序节点赋给目标节点的 next

此时,目标节点被其后序节点 next 完全覆盖,同时目标节点原来的后序节点 next 没指向它,即被删掉。

实现删除目标节点的目的,时间复杂度为 O(1)。

特殊场景:

1. 删除头节点

直接删除头结点,时间复杂度 O(1)。

2. 删除尾节点

需要从头结点开始遍历到尾部,对于 n-1 个中间节点,时间复杂度为 O(n)。

总的平均时间复杂度:

[(n-1) * O(1) + O(n)] / n 

结果还是 O(1)

代码实现

public static void deleteNodeInO1(Node head, Node target){
    if (head == null || target == null) {
        return;
    }
    //如果删除的是头结点,不管当前链表是否只有一个头结点,都直接赋 head.next,只有一个头结点时,
    //head.next 为 null
    if (head == target) {
        head = head.next;
    } else if (target.next != null) { //普通场景:覆盖抹除法
        Node nextNode = target.next;
        target.data = nextNode.data;
        target.next = nextNode.next;
    } else { //删除尾节点,只能从头遍历到导数第二个节点,再删除最后节点
        Node node = head;
        //注意,此处只能遍历到尾节点之前的一个节点,即倒数第二个节点,因为删除尾节点需要其前一个节点作为操作入口
        while (node.next != target) {
            node = node.next;
        }
        node.next = null; //此处 node.next 就是最后一个节点,将最后一个节点置空,就相当于删除最后节点
    }
}

总结

删除节点也可以使用 覆盖抹除法(自创词汇方便记忆),时间复杂度为 O(1)