剑指Offer面试题:12.在O(1)时间删除链表结点
一、题目:在O(1)时间删除链表结点
题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。
原文采用的是C/C++,这里采用C#,节点定义如下:
- public class Node<T>
- {
- // 数据域
- public T Item { get; set; }
- // 指针域
- public Node<T> Next { get; set; }
- public Node()
- {
- }
- public Node(T item)
- {
- this.Item = item;
- }
- }
要实现的DeleteNode方法定义如下:
- public static void DeleteNode(Node<int> headNode, Node<int> deleteNode)
- {
- }
二、解题思路
2.1 常规思路
在单向链表中删除一个结点,最常规的做法无疑是从链表的头结点开始,顺序遍历查找要删除的结点,并在链表中删除该结点。这种思路由于需要顺序查找,时间复杂度自然就是O(n)。
2.2 正确思路
是不是一定需要得到被删除的结点的前一个结点呢?答案是否定的。
我们可以很方便地得到要删除的结点的一下结点。因此,我们可以把下一个结点的内容复制到需要删除的结点上覆盖原有的内容,再把下一个结点删除,就相当于把当前需要删除的结点删除了。
但是,还有两个特殊情况需要进行考虑:
(1)如果要删除的结点位于链表的尾部,那么它就没有下一个结点:
此时我们仍然从链表的头结点开始,顺序遍历得到该结点的前序结点,并完成删除操作,这仍然属于O(n)时间的操作。
(2)如果链表中只有一个结点,而我们又要删除链表的头结点(也是尾结点):
此时我们在删除结点之后,还需要把链表的头结点设置为NULL。
最后,通过综合最坏情况(尾节点需要顺序查找,1次)和最好情况(n-1次),因此平均时间复杂度为:
需要注意的是:受到O(1)时间的限制,我们不得不把确保结点在链表中的责任推给了函数DeleteNode的调用者。
三、解决问题
3.1 代码实现
- public static void DeleteNode(Node<int> headNode, Node<int> deleteNode)
- {
- if (headNode == null || deleteNode == null)
- {
- return;
- }
- if (deleteNode.Next != null) // 链表有多个节点,要删除的不是尾节点:O(1)时间
- {
- Node<int> tempNode = deleteNode.Next;
- deleteNode.Item = tempNode.Item;
- deleteNode.Next = tempNode.Next;
- tempNode = null;
- }
- else if (headNode == deleteNode) // 链表只有一个结点,删除头结点(也是尾结点):O(1)时间
- {
- deleteNode = null;
- headNode = null;
- }
- else // 链表有多个节点,要删除的是尾节点:O(n)时间
- {
- Node<int> tempNode = headNode;
- while(tempNode.Next != deleteNode)
- {
- tempNode = tempNode.Next;
- }
- tempNode.Next = null;
- deleteNode = null;
- }
- }
3.2 单元测试
(1)封装返回结果
该方法作为单元测试的对比方法,主要用来对比实际值与期望值是否一致:
- public static string GetPrintNodes(Node<int> headNode)
- {
- if (headNode == null)
- {
- return string.Empty;
- }
- StringBuilder sbNodes = new StringBuilder();
- while(headNode != null)
- {
- sbNodes.Append(headNode.Item);
- headNode = headNode.Next;
- }
- return sbNodes.ToString();
- }
(2)测试用例
- // 链表中有多个结点,删除中间的结点
- [TestMethod]
- public void DeleteNodeTest1()
- {
- Node<int> head1 = new Node<int>();
- Node<int> head2 = new Node<int>();
- Node<int> head3 = new Node<int>();
- Node<int> head4 = new Node<int>();
- Node<int> head5 = new Node<int>();
- head1.Next = head2;
- head2.Next = head3;
- head3.Next = head4;
- head4.Next = head5;
- Program.DeleteNode(head1, head3);
- Assert.AreEqual(Program.GetPrintNodes(head1),"");
- }
- // 链表中有多个结点,删除尾结点
- [TestMethod]
- public void DeleteNodeTest2()
- {
- Node<int> head1 = new Node<int>();
- Node<int> head2 = new Node<int>();
- Node<int> head3 = new Node<int>();
- Node<int> head4 = new Node<int>();
- Node<int> head5 = new Node<int>();
- head1.Next = head2;
- head2.Next = head3;
- head3.Next = head4;
- head4.Next = head5;
- Program.DeleteNode(head1, head5);
- Assert.AreEqual(Program.GetPrintNodes(head1), "");
- }
- // 链表中有多个结点,删除头结点
- [TestMethod]
- public void DeleteNodeTest3()
- {
- Node<int> head1 = new Node<int>();
- Node<int> head2 = new Node<int>();
- Node<int> head3 = new Node<int>();
- Node<int> head4 = new Node<int>();
- Node<int> head5 = new Node<int>();
- head1.Next = head2;
- head2.Next = head3;
- head3.Next = head4;
- head4.Next = head5;
- Program.DeleteNode(head1, head1);
- Assert.AreEqual(Program.GetPrintNodes(head1), "");
- }
- // 链表中只有一个结点,删除头结点
- [TestMethod]
- public void DeleteNodeTest4()
- {
- Node<int> head1 = new Node<int>();
- Program.DeleteNode(head1, head1);
- head1 = null;
- Assert.AreEqual(Program.GetPrintNodes(head1), "");
- }
- // 链表为空
- [TestMethod]
- public void DeleteNodeTest5()
- {
- Program.DeleteNode(null, null);
- Assert.AreEqual(Program.GetPrintNodes(null), "");
- }
测试通过结果:
(3)代码覆盖率
剑指Offer面试题:12.在O(1)时间删除链表结点的更多相关文章
- 剑指Offer:面试题13——在O(1)时间删除链表结点
问题描述: 给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点.链表结点与函数的定义如下: public class ListNode{ int value; ListNode ...
- 【剑指Offer面试题】 九度OJ1518:反转链表
与其非常快写出一段漏洞百出的代码,倒不如细致分析再写出鲁棒的代码. 提前想好測试用例(输入非空等等)进行測试改动代码. 题目链接地址: http://ac.jobdu.com/problem.php? ...
- 剑指Offer面试题15(Java版):链表中倒数第K个结点
题目: 输入一个链表.输出该链表中倒数第k哥结点. 为了符合大多数人的习惯,本题从1開始计数.即链表的尾结点是倒数第1个结点. 比如一个链表有6个结点.从头结点開始它们的值依次是1.2.3,4,5, ...
- C++版 - 剑指offer 面试题5:从尾到头打印链表 题解
面试题5:从尾到头打印链表 提交网址: http://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tq ...
- 剑指Offer:面试题12——打印1到最大的n位数(java实现)
问题描述: 输入数字n,按顺序打印出从1到最大的n位十进制数,比如输入3,则打印出1,2,3一直到最大的3位数即999. 思路1:最简单的想法就是先找出最大的n位数,然后循环打印即可. public ...
- 【剑指offer 面试题12】打印1到最大的n位数
思路: 用n位字符数组表示n位数,通过递归的方式逐层(位)遍历,递归终止时打印. #include "stdio.h" #include "string.h" ...
- 剑指Offer面试题:4.从尾到头打印链表
一.题目:从尾到头打印链表 题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值. 到解决这个问题肯定要遍历链表.遍历的顺序是从头到尾的顺序,可输出的顺序却是从尾到头.也就是说第一个遍历到的结 ...
- 剑指Offer:面试题17——合并两个排序的链表
题目描述 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则. 思路1: 分别用p1,p2两个指针扫描两个有序链表,p3指针去构建新链表h3. p1.val & ...
- 剑指offer——面试题6:从尾到头打印链表
#include"iostream" #include"stdio.h" #include"stack" using namespace s ...
随机推荐
- [LINQ TO SQL]使用LINQ TO SQL创建数据库
这篇博客将介绍如何使用LINQ TO SQL来创建数据库,以及如何映射Table之间的主外键关系. 我们的数据库表关系如下: Province与City之间1:M,City与Area之间1:M的关系. ...
- mysql 连接池超时
var mysql = require('mysql'); var pool = mysql.createPool({ host: 'localhost', user: 'nodejs', passw ...
- 多线程之信号量(By C++)
信号量在多线程中,主要是用于线程的同步或者限制线程运行的数量. 所谓同步,当流程1运行在线程1中,流程2运行在线程2中,流程2必须在流程1结束之后才能开始执行.你会怎么做,所有就需要给出一个流程1结束 ...
- Java Web 项目目录规范
一.项目结构 这里和其他项目区别不大,我将模板抽离出来,更容易分析和理解: 解释一下:js主要包括extends(引入第三方的js).module(项目模块自己的js).lib(引用包,这里也可以继续 ...
- centos yum换阿里云源
阿里云Linux安装软件镜像源 阿里云是最近新出的一个镜像源.得益与阿里云的高速发展,这么大的需求,肯定会推出自己的镜像源. 阿里云Linux安装镜像源地址:http://mirrors.aliyun ...
- iphone中input标签会多出一块的解决办法
-webkit-appearance: none;
- css3 linear-gradient实现页面加载进度条效果
最终效果图: html结构: <div> <p class="p1"> <span></span> < ...
- php 用 http post方法传输数据
private function http_post($url,$post,$timeout){ $curl = curl_init(); curl_setopt($curl, CURLOPT_URL ...
- Java学习笔记1
学习一个Coursera的Data-structures-optimizing-performance. Working with String in Java Flesh score Flesh s ...
- JeeSite学习笔记~代码生成原理
1.建立数据模型[单表,一对多表,树状结构表] 用ERMaster建立数据模型,并设定对应表,建立关联关系 2.系统获取对应表原理 1.怎样获取数据库的表 genTableForm.jsp: < ...