技术标签: 笔记
给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间内删除该结点。假设要删除的结点确实在链表中。
常规思路:删除某个结点需要找到该结点的前一个结点,由于单向链表没有指向前一个结点的指针,所以不得不从头指针开始遍历链表。显然时间复杂度为O(n)。实现如下:
package Chap3;
public class DeleteNode {
private class Node {
int val;
Node next;
}
/**
* 常规方法,从first开始找到要删除结点的前一个结点,时间复杂度为O(n)
*/
public void deleteNode_2(Node first, Node toBeDel) {
if (first == null || toBeDel == null) {
return;
}
// 要删除的就是链表头,它没有前一个结点
if (first == toBeDel) {
first = first.next;
} else {
Node cur = first;
// 找到被删除结点的前一个结点
while (cur.next != toBeDel) {
cur = cur.next;
}
// cur为toBeDel的前一个结点
cur.next = cur.next.next;
}
}
}
试想一个简单例子,下面是一个链表,假设要删除的结点是C。按照上面的思路是从A开始遍历,找到D的前一个结点B后,然后令B.next = D。
A -> B -> C -> D
现在要实现O(1)的复杂度,肯定不能从头结点开始,试试直接从要删除的那个结点入手,因此A、B应该都不会被访问到。如果将D结点中的值(value)覆盖C中的值,就变成了下面这样
A -> B -> D(new) -> D(original)
此时再讲原来的D删除掉,就变成了下面这样,D(new)其实就是原来的C结点,只是值被替换了而已。这样我们只需用到被删除结点及其下一个结点就能实现O(1)时间删除。
A -> B -> D(new)
有一种特殊情况是:如果被删除结点是链表的最后一个结点,比如此时要删除D,就不能按照上面的方法来了,因为D的后面没有结点,其值不能被覆盖;此时还得从头结点开始找到D的前一个结点。
更特殊的情况:如果删除的结点既是最后一个结点又是头结点(只有一个结点的链表),那么直接将头结点置空即可。
/**
* 将toBeDel的下一个结点j的值复制给toBeDel。然后将toBeDel指向j的下一个结点
*/
public void deleteNode(Node first, Node toBeDel) {
if (first == null || toBeDel == null) {
return;
}
// 要删除的不是最后一个结点
if (toBeDel.next != null) {
Node p = toBeDel.next;
toBeDel.val = p.val;
toBeDel.next = p.next;
// 是尾结点也是头结点
} else if (first == toBeDel) {
first = first.next;
// 仅仅是尾结点,即在含有多个结点的链表中删除尾结点
} else {
Node cur = first;
while (cur.next != toBeDel) {
cur = cur.next;
}
cur.next = null;
}
}
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
注意重复的结点不保留:并不是将重复结点删除到只剩一个,而是重复结点的全部会被删除。所以链表1->2->3->3->4->4->5 处理后不是1->2->3->4->5。
思路:从头结点开始遍历链表,因为是删除结点,所以需要知道被删除结点的前一个结点,设为pre;只要当前结点和下一结点不为空,就比较它俩,如果相同,删除当前结点及其后所有值和它相同的结点(由于链表有序,所以值相同的结点必然连续)——只需将pre和第一个不和当前结点值相同的结点相连;**特殊情况是头结点就是重复结点,此时头结点也会被删除,因此需要重新定义头结点。**如果当前结点和下一个结点值不同,就更新当前结点和前一个结点pre,继续上述比较…
package Chap3;
public class DeleteDuplicationNode {
private class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
public ListNode deleteDuplication_2(ListNode pHead) {
if (pHead == null || pHead.next == null) {
return pHead;
}
// 当前结点的前一个结点
ListNode pre = null;
// 当前结点
ListNode cur = pHead;
while (cur != null && cur.next != null) {
// 如果当前结点和下一个结点值相同
if (cur.val == cur.next.val) {
int val = cur.val;
// 跳过所有和cur相同的值
while (cur != null && (cur.val == val)) {
cur = cur.next;
}
// 跳出循环得到的是第一个和cur.val不同的结点
// pre为空说明头结点就是重复结点,因此需要重新设置头结点
if (pre == null) pHead = cur;
// 否则cur之前的pre的下一个结点何cur连接
else pre.next = cur;
// 不相等就像前推进,更新cur和pre
} else {
pre = cur;
cur = cur.next;
}
}
return pHead;
}
}
还有种方法更为巧妙,一开始我们就为链表设置一个新的头结点,因为链表有序,所以将其值设置为原头结点的val -1
,这样保证了新的头结点的值与之后的所有结点都不会相同。因为是从原头结点开始遍历的,所以pre一开始理所应该地是新设置的头结点,如下:
// 建立一个头结点代替原来的pHead
ListNode first = new ListNode(pHead.val - 1);
first.next = pHead;
// 当前结点的前一个结点
ListNode pre = first;
// 当前结点
ListNode cur = pHead;
然后代码的主体和上面基本一样,只不过在头结点就是重复结点这种情况下,pre.next = cur
仍然是正确的,因为此时pre == first
,即first.next = cur
。效果等同于重新设置了原链表的头结点(新链表的first.next就是原链表的头结点)。最后记得返回的是first.next
,不能返回pHead,因为pHead有可能已经被删除了。
public ListNode deleteDuplication(ListNode pHead) {
if (pHead == null || pHead.next == null) {
return pHead;
}
// 建立一个头结点代替原来的pHead
ListNode first = new ListNode(pHead.val - 1);
first.next = pHead;
// 当前结点的前一个结点
ListNode pre = first;
// 当前结点
ListNode cur = pHead;
while (cur != null && cur.next != null) {
if (cur.val == cur.next.val) {
int val = cur.val;
while (cur != null && (cur.val == val)) {
cur = cur.next;
}
pre.next = cur;
} else {
pre = cur;
cur = cur.next;
}
}
// 这里不能返回pHead,因为pHead也可能被删除了
return first.next;
}
本文参考文献:
[1]github.com/haiyusun/data-structures
文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib
文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang
文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些
文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器
文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距
文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器
文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn
文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios
文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql
文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...
文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120
文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数