剑指offer 面试题18. 在O(1)时间内删除链表的节点
考查应聘者的创新思维能力。这道题要求应聘者打破常规的思维模式。
Tag
链表
考点
- 考查应聘者对链表的编程能力。
- 考查应聘者的创新思维能力。这道题要求应聘者打破常规的思维模式。当我们想删除一个节点时,并不一定要删除这个节点本身。可以先把下一个节点的内容复制出来覆盖被删除节点的内容,然后把下一个节点删除。这种思路不是很容易想到的。
- 考查应聘者思维的全面性。即使应聘者想到删除下一个节点这个办法,也未必能通过这轮面试。应聘者要全面考虑删除的节点位于链表的尾部及输入的链表只有一个节点这些特殊情况。
测试用例
- 功能测试
- 特殊测试:链表为空的情况、链表只有一个节点的情况、要删除的是尾节点,
Question
给定单向链表的头指针和一个节点指针,定义一个函数在 \(O(1)\) 时间内删除该节点。链表节点与函数的定义如下:
1 |
|
Intuition
思路分析:
常规来说,要删除一个节点 node
,就要找到其前驱节点 pre_node
,然后通过 pre_node.next = pre_node.next.next
来删除,如图(b)所示。但是顺序寻找的时间复杂度为 \(O(n)\),不满足条件。
这道题输入的就是一个节点指针,所以当我们要删除第 i
个节点时,由于已经有了第 i
个节点的节点指针,先把i
的下一个节点 j
的内容复制到i,然后把 i
的指针指向节点 j
的下一个节点。此时再删除节点 j
,其效果刚好是把节点 i
删除了,如图(c)所示。

特殊输入判定:
如果要删除的节点位于链表的尾部,那么它就没有下一个节点,怎么办?我们仍然从链表的头节点开始,顺序遍历得到该节点的前序节点,并按照图(b)的方法完成删除操作,这种特殊情况下时间复杂度为 \(O(n)\) 。 最后需要注意的是,如果链表中只有一个节点,而我们又要删除链表的头节点(也是尾节点),那么,此时我们在删除节点之后,还需要把链表的头节点设置为 nullptr
。
复杂度分析:
分析这种思路的时间复杂度。对于 n-1
个非尾节点而言,我们可以在 \(O(1)\) 时间内把下一个节点的内存复制覆盖要删除的节点,并删除下一个节点;对于尾节点而言,由于仍然需要顺序查找,时间复杂度是 \(O(n)\) 。因此,总的平均时间复杂度是 \([(n-1) \times O(1)+O(n)]/n\),结果还是 \(O(1)\) ,符合面试官的要求。
与面试官的讨论:
一般情况下,有关链表节点操作的问题,特殊判定时还要考虑节点不存在于该链表中的情况,但是由于本地要求在 \(O(1)\) 时间内删除节点,所以必须基于一个假设:要删除的节点的确在链表中。我们需要 \(O(n)\) 的时间才能判断链表中是否包含某一节点。受到 \(O(1)\) 时间的限制,我们不得不把确保节点在链表中的责任推给了函数的调用者。在面试的时候,我们可以和面试官讨论这个假设,这样面试官就会觉得我们考虑问题非常全面,同时也解释了为何不做链表判空。
Code
1 |
|
Question 2
思路
删除链表中重复的节点。在一个排序的链表中,如何删除重复的节点?这道题和 LeetCode-82-删除排序链表中的重复元素-II 是一样的,出现的重复数字都要去除掉,利用双指针解决即可。
注意须将正常情况放在 if-elif-else
的最后,先判定是否重复再做常规情况的处理。is_pure
用于标记所有节点都是重复的这种特殊情况。
测试用例
功能测试(重复的节点位于链表的头部/中间/尾部;链表中没有重复的节点)。 特殊测试(指向链表头节点的为 nullptr
指针;链表中所有节点都是重复的)。
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!