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

智能推荐

2022-12-09 Ubuntu16.4中访问另一台Ubuntu samba共享出来的目录方法_ubuntu挂载samba目录-程序员宅基地

文章浏览阅读1.3k次。【代码】2022-12-09 Ubuntu16.4中访问另一台Ubuntu samba共享出来的目录方法。_ubuntu挂载samba目录

【图像拼接/视频拼接】论文精读:Dynamic Video Stitching via Shakiness Removing-程序员宅基地

文章浏览阅读2.3w次,点赞14次,收藏17次。手持移动摄像机拍摄的拼接视频本质上可以增强普通用户的娱乐体验。然而,这样的视频通常包含严重的崎岖和大视差,这对缝合具有挑战性。在本文中,我们提出了一种新的视频拼接和稳定方法,用于移动设备捕获的视频。该方法的主要组成部分是一个统一的视频拼接和稳定优化,可以同时计算拼接和稳定,而不是单独计算拼接和稳定。通过这种方式,我们可以获得相对于彼此的最佳拼接和稳定结果,而不会对其中一个产生任何偏差。为了使优化具有鲁棒性,我们提出了一种识别输入视频背景的方法,以及它们的常见背景。这使我们能够仅对背景区域应用我们的优化,这是_dynamic video stitching via shakiness removing

详解斐波那契数列的递归与非递归实现(C语言版)_c语言斐波那契数列不用递归函数怎么写-程序员宅基地

文章浏览阅读1w次,点赞11次,收藏21次。1.斐波那契数列是什么:简单说,斐波那契数列就是一个数列从第3项开始,每一项都等于前两项之和。例子:0,1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368…2.非递归算法设计:采用for(当然while,do while也可以)循环,小编一开..._c语言斐波那契数列不用递归函数怎么写

Android achartengine timerchart曲线动态左移(横轴为当前时间)_android 曲线图 x轴随当前时间变化-程序员宅基地

文章浏览阅读2k次。zjk program//更新折线图 private void updatechart() { //判断当前点集中到底有多少点,因为屏幕总共只能容纳5个,所以当点数超过5时,长度永远是5 int length=series.getItemCount(); int a=length; if(length>5){ length=5; } ad_android 曲线图 x轴随当前时间变化

生成Git SSH公钥和私钥(ppk)文件_ssh ppk-程序员宅基地

文章浏览阅读7.7k次。说明:"userName"指我们克隆或制品库的登录用户名。_ssh ppk

回文日期-程序员宅基地

文章浏览阅读776次。这道题好像是一个蓝桥杯的省赛题,但是具体的是哪一个省的,我也不不记得了,也不想去搜了,反正今天就把它写到这里,有需要的博客主可以自行观看,好了,废话不多说,直接上题。题目描述:2020 年春节期间,有一个特殊的日期引起了大家的注意:2020 年 2 月 2 日。因为如果将这个日期按 “yyyymmdd” 的格式写成一个 8 位数是 20200202,恰好是一个回文数。我们称这样的日期是回文日期。有人表示 20200202 是“千年一遇” 的特殊日子。对此小明很不认同,因为不到 2 年之后就是下一个回文_回文日期

随便推点

网和aoe网的区别_AOV、AOE网络求解诀窍大公开,你看不懂算我输!-程序员宅基地

文章浏览阅读2.1k次。说到数据结构当中的图这一章节,除了图的基本概念、基本性质和几个看似复杂但是实际比较简单的算法(例如最小生成树、最短路径等)之外,最最让大家头疼的就是AOV网络和AOE网路了,不少学校将求解AOV、AOE的计算题或者画图简单题当作数据结构的压轴题目,这两个结构的求解算法也不像图章节中的其他问题那么简单。今天大牛学长就用一篇文章的篇幅教大家搞定AOV、AOE这一对难题。一、AOV网络1、A...

git 移动分支指针_git 分支( branch ) 的基本使用-程序员宅基地

文章浏览阅读3.4k次。分支( branches ) 是指在开发主线中分离出来,做进一步开发而不影响到原来主线。Git 存储的不是一系列的更改集( changeset ),而是一系列快照。当你执行一次 commit 时, Git 存储一个 commit 对象,它包含一个指针指向你当前需要提交的内容的快照。Git 中的 master 分支的功能,和其他分支一样。master 在 git 项目中常见到,是因为 git ini..._git branches

PT 练习 3.5.1:基本 VLAN 配置 (含答案)_pt练习3.5.1,基本vlan配置答案-程序员宅基地

文章浏览阅读8.4k次,点赞11次,收藏65次。PT 练习 3.5.1:基本 VLAN 配置拓扑图地址表设备 接口 IP 地址 子网掩码 默认网关S1VLAN 99172.17.99.11255.255.255.0不适用S2VLAN 99172.17.99.12255.255.255.0不适用S3VLAN 99172.17.99.13255.255.255.0不适用PC1网卡172.17.10.21255.255.255.0172.17.10.1PC2网卡1.._pt练习3.5.1,基本vlan配置答案

java 开发计算精度的问题_java bigdecimal精度问题-程序员宅基地

文章浏览阅读754次。所以,在涉及到精度计算的过程中,我们尽量使用String类型来进行转换,正确用法如下。_java bigdecimal精度问题

PlantUML画类图、流程图、时序图使用详解(转载)_plantuml流程图中从后面的流程指向前面的-程序员宅基地

文章浏览阅读740次。**https://blog.csdn.net/geduo_83/article/details/86422485**版权声明:本文为CSDN博主「门心叼龙」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/geduo_83/java/article/details/86422485..._plantuml流程图中从后面的流程指向前面的

网易 盖楼 实现_网易严选宣布退出双十一!是真心还是刷热度?_ 电商动态-程序员宅基地

文章浏览阅读49次。11月5日消息,随着双十一的脚步越来越近,各大平台们铆足了劲地为自己争取更大的流量。4日晚上,网易严选发布公告称,今年的双十一“想给它泼一盆‘冷水’”,将退出“鼓吹过度消费、为销售数字狂欢”的双十一。网易严选不同寻常的做法,倒是吸引了不少人的眼光。网易严选公告表示,今年双十一,不做复杂优惠玩法,没有养猫盖楼、组队PK、手势地图,但为消费者搞定了全年最大力度补贴,为严选双11当天准备的商品是全年抄底...

推荐文章

热门文章

相关标签