Java详解剑指offer面试题18--删除链表的结点_java创建一个单项循环链表,每经过三个结点,就删除第三个结点 → 找到最后遗留的一-程序员宅基地

技术标签: 笔记  

Java详解剑指offer面试题18——删除链表的结点

题目一——O(1)删除链表结点

给定单向链表的头指针和一个结点指针,定义一个函数在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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wq6ylg08/article/details/108429934

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读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

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签