【C#刷题】| 作者 / Edison Zhou
我们来用之前学到的数据结构知识来刷《剑指Offer》的一些核心题目(精选了其中30+道题目),希望对你有帮助!本文题目为:在O(1)时间删除链表节点。
1题目介绍
题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。
《剑指Offer》一书中使用的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)。
3解法2:正确思路
是不是一定需要得到被删除的结点的前一个结点呢?答案是否定的。
我们可以很方便地得到要删除的结点的下一个结点。因此,我们可以把下一个结点的内容复制到需要删除的结点上覆盖原有的内容,再把下一个结点删除,就相当于把当前需要删除的结点删除了。
但是,还有两个特殊情况需要进行考虑:
(1)如果要删除的结点位于链表的尾部,那么它就没有下一个结点:
此时我们仍然从链表的头结点开始,顺序遍历得到该结点的前序结点,并完成删除操作,这仍然属于O(n)时间的操作。
(2)如果链表中只有一个结点,而我们又要删除链表的头结点(也是尾结点):
此时我们在删除结点之后,还需要把链表的头结点设置为NULL。
最后,通过综合最坏情况(尾节点需要顺序查找,1次)和最好情况(n-1次),因此平均时间复杂度为O(1):
需要注意的是:受到O(1)时间的限制,我们不得不把确保结点在链表中的责任推给了方法DeleteNode的调用者。
下面是DeleteNode方法的C#代码实现:
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;}
}
4单元测试
老规矩,仍然需要为我们的代码写单元测试,这也是一个好习惯哦!
首先,为了后面测试验证方便,这里我们先封装一个测试辅助方法GetPrintNodes用于对比实际值与期望值是否一致:
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();
}
其次,测试代码:
// 链表中有多个结点,删除中间的结点
[TestMethod]
public void DeleteNodeTest1()
{Node<int> head1 = new Node<int>(1);Node<int> head2 = new Node<int>(2);Node<int> head3 = new Node<int>(3);Node<int> head4 = new Node<int>(4);Node<int> head5 = new Node<int>(5);head1.Next = head2;head2.Next = head3;head3.Next = head4;head4.Next = head5;Program.DeleteNode(head1, head3);Assert.AreEqual(Program.GetPrintNodes(head1),"1245");
}// 链表中有多个结点,删除尾结点
[TestMethod]
public void DeleteNodeTest2()
{Node<int> head1 = new Node<int>(1);Node<int> head2 = new Node<int>(2);Node<int> head3 = new Node<int>(3);Node<int> head4 = new Node<int>(4);Node<int> head5 = new Node<int>(5);head1.Next = head2;head2.Next = head3;head3.Next = head4;head4.Next = head5;Program.DeleteNode(head1, head5);Assert.AreEqual(Program.GetPrintNodes(head1), "1234");
}// 链表中有多个结点,删除头结点
[TestMethod]
public void DeleteNodeTest3()
{Node<int> head1 = new Node<int>(1);Node<int> head2 = new Node<int>(2);Node<int> head3 = new Node<int>(3);Node<int> head4 = new Node<int>(4);Node<int> head5 = new Node<int>(5);head1.Next = head2;head2.Next = head3;head3.Next = head4;head4.Next = head5;Program.DeleteNode(head1, head1);Assert.AreEqual(Program.GetPrintNodes(head1), "2345");
}// 链表中只有一个结点,删除头结点
[TestMethod]
public void DeleteNodeTest4()
{Node<int> head1 = new Node<int>(1);Program.DeleteNode(head1, head1);head1 = null;Assert.AreEqual(Program.GetPrintNodes(head1), "");
}// 链表为空
[TestMethod]
public void DeleteNodeTest5()
{Program.DeleteNode(null, null);Assert.AreEqual(Program.GetPrintNodes(null), "");
}
最后,测试结果如下:
代码覆盖率如下:
Ref参考资料
何海涛,《剑指Offer》
后台回复:剑指offer,即可获得pdf下载链接哟!
专注于开发技术与个人成长分享,
做对你有用的公众号!
????点击获取文章源码