LeetCode-141环形链表 LeetCode-142环形链表二

news2025/4/8 20:45:41

一、前言

本篇文章在我之前讲完的链表、链表与递归的基础上进行讲解,本次我们以leetcode为例,讲解链表的其他题型,今天我们先了解一下环形链表,这里我们以leetCode141和leetCode142为例。

二、LeetCode141

在这里插入图片描述
首先关于这道题,我们知道链表有着天然的递归性质,我们可以借助哈希表,如果链表有环,那么当通过环走到相同节点的时候,在哈希表一定能找到。所以对于递归求解,最基本的问题就是分为有环和无环,如果无环,那么当当前节点等于NULL,直接return false即可。如果有环,那么当hashSet找到此节点时,直接return true即可。然后把head.next作为参数调用递归函数即可。
代码如下:

public static boolean hasCycle(ListNode head) {
		if (head == null || head.next == null) {
			return false;
		}
		HashSet<ListNode> hashSet = new HashSet<>();
		return hasCycleRecursion(hashSet,head);
	}

	private static boolean hasCycleRecursion(HashSet<ListNode> hashSet, ListNode head) {
		if(head == null){
			return false;
		}
		if(hashSet.contains(head)){
			return true;
		}
		hashSet.add(head);
		return hasCycleRecursion(hashSet,head.next);
	}

在这里插入图片描述
那么这道题有没有更优解呢,答案是有的,这里我们就要介绍一种特殊的算法:快慢指针。
快慢指针的原理,就是让快慢指针同时从head出发,快指针一次走两格,而慢指针一次走一格。
如果链表无环,那么快指针肯定会走到NULL,从而快慢不可能相遇。如果链表有环,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。因为它们的相差速度就是一,当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。还不懂的话,我们以相对速度的角度去看,此时slow不动,fast每次以一的速度去追slow,那么这样肯定是能追上slow的。
在这里插入图片描述
那么这样就很简单了,我们定义slow和fast都从head出发,只要fast.next和fast.next.next均不为空,那么就让slow和fast开始移动,如果在移动过程中slow==fast,那么就说明有环,否则无环:

  public static boolean hasCycle(ListNode head) {
        if(head == null){
            return false;
        }
        ListNode slow = head;
        ListNode fast = head;
        while (fast.next != null && fast.next.next != null){
            slow = slow.next;
            fast = fast.next.next;
            if(fast == slow){
                return true;
            }
        }
        return false;
    }

在这里插入图片描述
有些人可能会问,快慢指针为什么快指针一定要是慢指针的两倍,我不可以是三倍吗
其实三倍也可以,只是反而可能拖累效率,推导如下
在这里插入图片描述
如上图,非环长s,环长t,环入口距离相遇点j,假设快指针比慢指针速度快k倍,当两者相遇,此时慢指针走了s+j,快指针走了
s+j+n*t(n为快指针在环中的圈数)。
那么有s+j+n*t=k(s+j),推导一下有(n*t)/(k-1)=s+j,也可以写为(n/k-1)*t = s+j
我们知道这个式子的参数一定都是整数,那么k-1也一定要是正整数,在其他量都确定的情况下,保持(n / k-1)不变的话,分母k-1越小,则分子n也越小,对应快指针在环中走的圈数n也越小,k=2,3,4···,其中k=2时,m最小,即最快相遇,这样效率最高。如果k=3,反而可能影响效率。
故我们快指针设定一次走两步。

三、LeetCode142

那么如果我们需要找到环入口该如何去做呢,拿LeetCode142为例:
在这里插入图片描述
做法一,其实上面的哈希表大家仔细想想,已经做完了这道题,当我发现哈希表重复的节点的时候,那个重复的节点就是环入口。
所以把141的代码稍微改下返回值就可以提交了:

public ListNode detectCycle(ListNode head) {
		if(head == null || head.next == null){
			return null;
		}
		HashSet<ListNode> listNodes = new HashSet<>();
		return detectCycleRecursion(listNodes,head);
	}

	private ListNode detectCycleRecursion(HashSet<ListNode> listNodes, ListNode head) {
		if(head == null){
			return null;
		}
		if(listNodes.contains(head)){
			return head;
		}
		listNodes.add(head);
		return detectCycleRecursion(listNodes,head.next);
	}

在这里插入图片描述
那么解法二自然还是之前的快慢指针了,我们首先定义快慢指针,然后沿用判环的代码,如果快慢指针相遇,说明有环,此时在这个基础上,我们来推导环的入口:
在这里插入图片描述
设a为head到环入口的距离,环长为t
此时快指针比慢指针多走nt步(因为slow和fast都走了a步),设立f为fast走的,s为slow走的,有f=2s,f=s+nt
由上面的结论可以得到s=nt,即慢指针走了nt距离
假设从链表头部一直向前走并统计步数k,那么所有 走到链表入口节点时的步数 是:k=a+nt
那么根据上面s=nt,头节点走到环入口是a+nt,我们只需要让慢指针再走a步即可达到环入口而头节点距离环入口刚好是a。那么就是头节点和慢指针一起出发,相遇的节点就是环入口
所以当快慢指针相遇的时候,我们定义两个指针,分别从head和slow出发,一次走一步,相遇点就是环入口,代码如下:

	public static ListNode detectCycle(ListNode head) {
		if(head == null){
			return null;
		}
		ListNode slow = head;
		ListNode fast = head;
		while (fast.next != null && fast.next.next != null){
			slow = slow.next;
			fast = fast.next.next.next;
			if(slow == fast){
				ListNode newSlow = slow;
				ListNode newHead = head;
				while (newSlow != newHead){
					newSlow = newSlow.next;
					newHead = newHead.next;
				}
				return newHead;
			}
		}
		return null;
	}

在这里插入图片描述
这里最后提一嘴,如果要求环长,首先当然还是可以用hash表,和上面思路一样,当发现一样的元素时,此时看哈希表的长度即可。
而另一种方法就是当快慢指针相遇时,由于此时肯定是有环的,我只需要让慢指针一格一格往前走,快指针不动,当慢指针再次和快指针相遇时,此时慢指针走的距离就是环长。这里代码就不做赘述,比较简单,大家可以自行实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1356212.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MongoDB】关于MongoDB更新文档update的操作,十分详细,建议收藏!!!

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;MongoDB数据库学习 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继…

IPv6邻居发现协议(NDP)---路由发现

IPv6路由发现(前缀公告) 邻居发现 邻居发现协议NDP(Neighbor Discovery Protocol)是IPv6协议体系中一个重要的基础协议。邻居发现协议替代了IPv4的ARP(Address Resolution Protocol)和ICMP路由器发现(Router Discovery),它定义了使用ICMPv6报文实现地址解析,跟踪邻…

60.网游逆向分析与插件开发-游戏增加自动化助手接口-游戏公告功能的逆向分析与测试

内容来源于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;文字资源读取类的C还原-CSDN博客 码云地址&#xff08;master分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;878db7708de09b448010ef54526fe…

手把手教你如何使用SpringBoot3打造一个个性化的代码生成器

自定义代码生成器 代码基于SpringBoot3、Vue3、highlight实现自定义代码生成功能 SpringBoot3.x、MySQL8、MyBatisPlus3.5.x、velocity2.x、SpringSecurity6.x、Vue3、TypeScript、highlight demo所需要的依赖及其对应版本号 pom <?xml version"1.0" encoding&…

运用Jmeter进行登录测试

开始了解Jmeter,写篇关于Jmeter的博客做备忘,这里以苏宁易购网站的登录请求为例实战来说明测试计划元件,创建一个 Web 测试计划。 今天简单介绍Jemeter的入门,Jmeter 的安装这边就跳过,直接讲述如何使用JMETER,如何运用Jmeter进行测试。 a.下载jmeter软件 b.安装…

一起offsetLeft值引发的样式错乱问题

问题描述&#xff1a; 首先我们来看一下正常样式和异常样式&#xff0c;正常样式的左侧菜单会正常显示出来&#xff0c;而异常样式的左侧菜单会被覆盖&#xff1b; 正常的样式&#xff1a; 异常的样式&#xff1a; 问题探索&#xff1a; 左边的间距其实跟通过读取最外层元素…

mysql(三) 索引-普通索引、复合索引、索引规则等

上文我们学习了索引基础知识、所以我画了一个查询语句简单的执行流程、希望可以帮助大家一起学习。 目录 mysql select语句执行流程 普通索引 复合索引 创建索引&#xff08;三种&#xff09; 1、使用INDEX建表的时候创建索引 (创建表时建索引) INDEX的语法&#xff1a; …

osg-材质 (osg::Material)

1.材质类 材质类 (osg::Material)继承自osg::StateAttribute 类。osg::Material 封装了 OpenGL的 glMaterial()和glColorMaterial()指令的函数功能&#xff0c;其继承关系图如图5-27 所示。 图 5-27 osg::Material 的继承关系图 在场景中设置节点的材质属性&#xff0c;首先要…

VS2017 CMake编译Opencv

先下载opencv4.2.0源码以及opencv_contrib-4.2.0 地址链接&#xff1a;https://pan.baidu.com/s/1AgFsiH4uMqTRJftNXAqmTw?pwd3663 提取码&#xff1a;3663 先建立一个opencv_debug和opencv_release文件夹这两个都是为了后续存放编译好的debug版本和release版本opencv的&#…

APP自动化测试工具:八款推荐解析

uiautomator2 github地址&#xff1a;github.com/openatx/uia… UiAutomator 是 Google 提供的用来做安卓自动化测试的一个 Java 库&#xff0c;基于 Accessibility 服务。功能很强&#xff0c;可以对第三方 App 进行测试&#xff0c;获取屏幕上任意一个 APP 的任意一个控件属…

LeetCode---378周赛

题目列表 2980. 检查按位或是否存在尾随零 2981. 找出出现至少三次的最长特殊子字符串 I 2982. 找出出现至少三次的最长特殊子字符串 II 2983. 回文串重新排列查询 一、检查按位或是否存在尾随零 这题和位运算有关&#xff0c;不是很难&#xff0c;题目要求至少有两个数的…

基于SSM的新闻网站

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

基于SSM的班级事务管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

WebSocket的优点和缺点:一文详解。

WebSocket 的优缺点 WebSocket 协议是一种双向通信协议&#xff0c;它使用单个 TCP 连接实现全双工通信&#xff0c;这使它比传统的 HTTP 协议更有效率。 WebSocket 优点 双向通信&#xff1a; WebSocket 协议支持双向通信&#xff0c;使服务器和客户端之间的通信更加方便和快…

googlecode.log4jdbc慢sql日志,格式化sql

前言 无论使用原生JDBC、mybatis还是hibernate&#xff0c;使用log4j等日志框架可以看到生成的SQL&#xff0c;但是占位符和参数总是分开打印的&#xff0c;不便于分析&#xff0c;显示如下的效果: googlecode Log4jdbc 是一个开源 SQL 日志组件&#xff0c;它使用代理模式实…

图像预处理——transforms

一、transforms 运行机制 torchvision是PyTorch的一个扩展库&#xff0c;提供了许多计算机视觉相关的工具和功能。下面是关于torchvision中常用模块的介绍&#xff1a; torchvision.transforms&#xff1a;提供了一系列常用的图像预处理方法&#xff0c;用于对图像进行变换、…

【生成人工智能】Ray如何解决生成人工智能基础设施的常见生产挑战

这是我们生成人工智能博客系列的第一部分。在这篇文章中&#xff0c;我们讨论了如何使用Ray来生产常见的生成模型工作负载。即将发布的一篇博客将深入探讨Alpa等项目为什么要使用Ray来扩展大型模型。 生成的图像和语言模型有望改变企业的设计、支持、开发等方式。本博客重点关…

Spring——Spring IOC(2)

1.Spring中的工厂类 1.1 ApplicationContext ApplicationContext的实现类&#xff0c;如下图&#xff1a; ClassPathXmlApplicationContext&#xff1a;加载类路径下 Spring 的配置文件FileSystemXmlApplicationContext&#xff1a;加载本地磁盘下 Spring 的配置文件 1.2 B…

基于Segformer实现PCB缺陷检测(步骤 + 代码)

导 读 本文主要介绍基于Segformer实现PCB缺陷检测 &#xff0c;并给出步骤和代码。 背景介绍 PCB缺陷检测是电子制造的一个重要方面。利用Segformer等先进模型不仅可以提高准确性&#xff0c;还可以大大减少检测时间。传统方法涉及手动检查&#xff0c;无法扩展且容易出错…

体元法--体积计算

文章目录 环境&#xff1a;1.1 体元法介绍&#xff1a;2.1 python代码3.1 可视化 环境&#xff1a; Open3D 1.1 体元法介绍&#xff1a; 用一个个体素去占据点云&#xff0c;然后对所有体素求和 2.1 python代码 conda activete deeplabv3plus(环境名称–安装好open3D的) py…