【面试经典150 | 链表】循环链表

news2024/11/16 1:49:29

文章目录

  • Tag
  • 题目来源
  • 题目解读
  • 解题思路
    • 方法一:哈希集合
    • 方法二:快慢指针
    • 方法三:计数
  • 拓展
  • 其他语言
    • python3
  • 写在最后

Tag

【快慢指针】【哈希集合】【计数】【链表】


题目来源

141. 环形链表


题目解读

判断一个链表中是否存在环。


解题思路

链表中有环,那么在遍历链表中节点的时候就有部分元素被重复遍历,于是可以想到使用一个集合来记录已经遍历的节点,如果集合中有节点在遍历的时候第二次出现,那么一定存在环。该方法看似可以判断环的问题,但是链表中的节点会存在重复值,在无环的链表中也可能存在某些节点值被遍历了两次,导致无环链表被误判成有环链表。

如果集合中存放的是节点而不是节点的值呢?这样就可以唯一表示每一个节点了,因为每一个节点实际上是一个结构体(c语言中)或者类(c++中),都是以地址的形式存在于栈上。

接下来将介绍三种判断链表中是否有环的方法,方法一是哈希集合的方法,方法二是一种数学上的方法,方法三是一种 “巧妙” 的方法。这三种方法都推荐大家认真学习,务必要掌握。

方法一:哈希集合

遍历链表,将经过的节点存到哈希集合中,如果集合中已经存在了该节点,就说明链表中存在环。

实现代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> st;
        while (head) {
            if (st.count(head)) {
                return true;
            }
            st.insert(head);
            head = head->next;
        }
        return false;
    }
};

复杂度分析

时间复杂度: O ( N ) O(N) O(N),其中 N N N 是链表中的节点数。最坏情况下我们需要遍历每个节点一次。

空间复杂度: O ( N ) O(N) O(N),其中 N N N 是链表中的节点数。主要为哈希表的开销,最坏情况下我们需要将每个节点插入到哈希表中一次。

方法二:快慢指针

我们可以使用快慢指针来判断是否有环。定义一个慢指针 slow 每次只移动一个位置,定义一个快指针 fast 每次移动两个位置,两个指针从同一个节点出发:

  • 如果链表中没有环,快指针一定会到达最后的 nullptr 节点;
  • 如果链表中有环,那么两个指针一定会在环内相遇。

实现代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if (head == nullptr || head->next == nullptr)
			return false;
		ListNode *slow = head;
		ListNode *fast = head->next;
		while (fast != slow) {
			if (fast == nullptr || fast->next == nullptr)
				return false;
			slow = slow->next;
			fast = fast->next->next;
		}
		return true;
    }
};

代码中的 while 循环的条件语句是 slow != fast,即快慢指针相等的时候才会退出循环。我们初始化的慢指针为 head,快指针为 head->next,如果我们将两个指针初始都置于 head,那么 while 循环就不会执行,才会对两指针进行上述的初始化,相当于快慢指针是从 dummy head(头结点的虚构的上一个)这个节点同时出发的,我们先让快慢指针出发了各自的一个步幅,这样就可以使用 while 循环了。

当然也可以使用 do...while 循环,这样的话快慢指针初始化为 head 就可以了。

while 语句与 do...while 语句的区别在于前者是先判断再执行,而后者是先执行再判断。

复杂度分析

时间复杂度: O ( N ) O(N) O(N),其中 N N N 是链表中的节点数。

空间复杂度: O ( 1 ) O(1) O(1)

方法三:计数

方法二就比较简单了。如果链表中存在环,那么在遍历链表的时候一定有一些节点值被重复遍历了多次。由于本题中链表最多有 1 e 4 1e4 1e4 个节点,因此我们边遍历节点边计数(从头结点到当前节点的个数),如果数量大于 1 e 4 1e4 1e4,那么链表中一定存在环,我们直接退出迭代遍历的过程,返回 false

实现代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        int cnt = 0;
        while (head) {
            ++cnt;
            if (cnt > (int)1e4) {
                return true;
            }
            head = head->next;
        }
        return false;
    }
};

复杂度分析

时间复杂度: O ( N ) O(N) O(N),其中 N N N 是链表中的节点数。我们只有遍历每个节点一次之后才能判断出有没有环。

空间复杂度: O ( 1 ) O(1) O(1)


拓展

本题中如果还需要求有环链表中的入环点,那么该怎么解决呢?也就是 142. 环形链表 II 这个题目要解决的问题。

最简单的方法还是哈希集合的方法,当某个节点第二次出现,该节点就是要求的入环点,该方法很简答这里不予说明。

重点来看一下如何使用 快慢指针法 来求入环点。

先来假设一些变量,入环点之前的距离为 a,入环点到快慢指针第一相遇的点的距离为 b,此时环中剩下的距离为 c,快指针在环中走了 n n n 圈之后,快、慢指针第一次相遇,于是快慢指针行走的位置距离为:

  • 慢指针:a+b
  • 快指针:a+b+n(b+c)

慢指针每次走一个节点位置,快指针每次走两个节点位置,快指针走的路程就是慢指针的两倍,所以快慢指针行走的位置距离有这样的关系:

2 ( a + b ) = a + b + n ( b + c ) 2(a+b)=a+b+n(b+c) 2(a+b)=a+b+n(b+c)

化简得到:

c = a + b + ( b + c ) ( n − 1 ) c = a+b+(b+c)(n-1) c=a+b+(b+c)(n1)

根据以上公式推导可以知道,当入环点到第一次相遇点的距离加上 n − 1 n-1 n1 圈的环的长度等于链表头部到入环点的距离。进一步来讲,如果两个指针分别从头结点和第一次相遇点出发,每次行进一个节点,那么它们相遇的节点(第一次相遇)就是入环点。

实现代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if (head == NULL || head->next == NULL || head->next->next == NULL) {
            return NULL;
        }

        ListNode *slow = head->next, *fast = head->next->next;
        while (slow != fast) {
            if (fast->next == NULL || fast->next->next == NULL) {
                return NULL;
            }
            slow = slow->next;
            fast = fast->next->next;
        }

        fast = head;
        while (slow != fast) {
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
};

其他语言

python3

以下是 快慢指针法 判断链表中是否有环的python3语言代码

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        if not head or not head.next:
            return False
        
        slow = head
        fast = head.next
        while fast != slow:
            if not fast or not fast.next:
                return False
            slow = slow.next
            fast = fast.next.next
        return True

写在最后

如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。

最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。

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

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

相关文章

vue2中,下拉框多选和全选的实现

vue2中&#xff0c;下拉框多选和全选的实现 代码布局在methods: 中添加功能函数较为完整的一个整体代码&#xff1a; 如图所示点击全选即可完成下拉框中全部子项的全部的选中&#xff0c;同时取消全选即可全部取消选择。 代码布局 <div class"chos-box2"><…

STM32入门F4

学习资料&#xff1a;杨桃电子&#xff0c;官网&#xff1a;洋桃电子 | 杜洋工作室 www.doyoung.net 嵌入式开发核心宗旨&#xff1a;以最适合的性能、功能、成本来完成最有性价比的产品开发。 1.为什么要学F407 STM32F103系列与STM32F407系列对照表&#xff1a; 2.F4系列命…

Ruo-Yi前后端分离版本相关笔记

1.前提条件和基础 Spring Boot Vue 环境要求&#xff1a;Jdk1.8以上版本、MySql数据库、Redis、Maven、Vue 2.使用若依 官网地址&#xff1a;RuoYi-Vue: &#x1f389; 基于SpringBoot&#xff0c;Spring Security&#xff0c;JWT&#xff0c;Vue & Element 的前后端分…

渗透测试tomcat错误信息泄露解决办法

解决方法&#xff1a; 1、使用tomcat8.5.16&#xff0c;会重定向非法url到登录url 2、配置server.xml&#xff0c;加上 <Valve className"org.apache.catalina.valves.ErrorReportValve" showReport"false" showServerInfo"false" />配置…

Unity中国、Cocos为OpenHarmony游戏生态插上腾飞的翅膀

2023年是OpenHarmony游戏生态百花齐放的一年&#xff01;为了扩展OpenHarmony游戏生态&#xff0c;OpenHarmony在基金会成立了游戏SIG小组&#xff0c;游戏SIG小组联合cocos&#xff0c;从cocos2dx入手一周内快速适配了cocos2.2.6的MVP版本&#xff0c;随后又分别适配了cocos2d…

扬尘在线监测 扬尘在线监测系统 扬尘监测设备

计讯物联扬尘在线监测系统&#xff0c;环保数采仪云管理平台&#xff0c;PM2.5、PM10、TSP、温度、湿度、噪声、大气压力、风力、风速、风向监测目标数据采集&#xff0c;5G/4G无线网络自动上报云端&#xff0c;相关部门实时远程在线监控&#xff0c;同时支持治污喷淋系统、视频…

msvcr100.dll丢失怎样修复,msvcr100.dll丢失怎么解决(最新方法分享)

我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcr100.dll丢失”。这个错误通常会导致某些程序无法正常运行。为了解决这个问题&#xff0c;我们需要采取一些措施来修复丢失的msvcr100.dll文件。本文将介绍五个有效的解决方法&#xff0c;帮助大家解决这一问题。 一…

OKLink携手CertiK在港举办Web3生态安全主题论坛

2023年10月23日&#xff0c;OKLink与CertiK共同发起的Web3生态安全主题论坛在香港铜锣湾拉开帷幕。本次论坛由OKLink和CertiK主办&#xff0c;香港投资推广署独家支持&#xff0c;聚焦如何构建安全可靠的Web3生态系统议题&#xff0c;同时深入剖析这一进程中所面临的潜在挑战。…

Qt中QFile、QByteArray QDataStream和QTextStream区别及示例

在Qt中&#xff0c;QFile、QByteArray、QDataStream和QTextStream是常用的文件和数据处理类。 主要功能和区别 QFile&#xff1a; QFile是用于读写文本和二进制文件以及资源的I/O设备。可以单独使用QFile&#xff0c;或者更方便地与QTextStream或QDataStream一起使用。 通常在…

开关电源检测的技术标准和安全标准是什么?纳米软件为您介绍

开关电源总体技术标准 1. 外观&#xff1a;元器件排列整齐、美观、结构合理&#xff0c;焊点均匀饱满、明亮、光滑、无尖刺&#xff0c;焊接牢固。PCB板铜条无脱落、外露、无毛刺、飞边、变形。 2. 输入电压&#xff1a;110VAC/DC或220VAC/DC或380VAC三相20%;或85~264VAC全范围…

Go学习第六章——系统函数与错误处理

Go系统函数与错误处理 1 字符串相关系统函数2 时间和日期相关的函数2.1 Now函数2.2 日期的格式化 3 内置函数4 错误处理4.1 基本使用4.2 自定义错误 1 字符串相关系统函数 下面给出20个常见的函数&#xff0c;不需要导包&#xff0c;直接可以用 func main() {// 1.统计字符串…

Synchronized同步锁

synchronized 一&#xff0c;介绍 Java中的synchronized关键字用于实现线程同步&#xff0c;可以修饰方法或代码块。 1. 修饰方法&#xff1a;当一个方法被synchronized修饰时&#xff0c;只有获得该方法的锁的线程才能执行该方法。其他线程需要等待锁的释放才能执行该方法。…

Java Netty - Buffer类

Buffer类 当应用程序进行数据传输的时候&#xff0c;往往需要使用缓冲区&#xff0c;常用的缓存区就是JDK NIO类库提供的 java.nio.Buffer&#xff1b; NIO的Buffer本质上是一个内存块&#xff0c;既可以写入数据&#xff0c;也可以从中读取数据&#xff1b; 其中&#xff0c;…

IP地址定位是什么?有哪些优缺点?

IP地址定位是一种用于确定设备或用户地理位置的方法&#xff0c;具有一些明显的优点和缺点。以下是IP地址定位的优缺点&#xff1a; 优点&#xff1a; 广泛适用性&#xff1a; IP地址定位适用于几乎所有与互联网连接的设备&#xff0c;包括计算机、智能手机、平板电脑和物联网…

全链路压测专题---2、全链路压测架构和技术

如何开展全链路压测 业务模型梳理 首先应该将核心业务和非核心业务进行拆分&#xff0c;确认流量高峰针对的是哪些业务场景和模块&#xff0c;针对性的进行扩容准备梳理出对外的接口&#xff1a;使用MOCK&#xff08;模拟&#xff09;方式做挡板千万不要污染正常数据&#xf…

包装类知识.JDK7,JDK8相关时间类练习

包装类 包装类:基本数据类型对应的引用类型 JDK5之前的Integer包装类 如下 了解即可 i5传入的值 第一个是字符串,第二个是进制数.为83是因为再打印的时将123看成8进制了 然后打印转化为10进制 区别对比 public static void main(String[] args) {Integer i1 Integer.valu…

5年经验之谈 —— 手把手教你接口性能测试之工具篇!

本文是我们《手把手教你接口性能测试》系列文章中的中篇&#xff0c;主要介绍软件测试工作中常用的一些接口测试工具。 一、接口都有哪些类型&#xff1f; 1、接口一般分为两种&#xff1a; 1.程序内部的接口 2.系统对外的接口 程序内部的接口 方法与方法之间&#xff0c;模…

最靠谱的nvm安装教程 for mac

nvm是node 版本管理工具 一、前提 1、保证自己的mac电脑上没安装node环境 2、保证自己的电脑上有安装git&#xff0c;不然下载nvm时会报错。 git下载可以参考&#xff1a; 1&#xff09;佛系安装&#xff1a;通过提示下载 XCode 2&#xff09;brem安装 3&#xff09;终极大法&…

Kubernetes - 一键安装部署 K8S(附:Kubernetes Dashboard)

问题描述 不知道大伙是如何安装 K8s&#xff0c;特别还是集群的时候&#xff0c;我上一次安装搭建的时候&#xff0c;那个恶心到我了&#xff0c;真的是一步一个脚印走完整个搭建流程&#xff0c;爬了不少坑。 于是&#xff0c;才有了今天的文章&#xff0c;到底有没有可以一…

在mybatis的xml中使用枚举来做判断条件

1.枚举类 import com.baomidou.mybatisplus.annotation.IEnum; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; import com.shinkeer.common.utils.StringUtils;import java.util.HashMap; import java.util.Map;…