环形链表[简单]

优质博文:IT-BLOG-CN

一、题目

给你一个链表的头节点head,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪next指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数pos来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回true否则,返回false

快慢指针

可以使用快慢指针法, 分别定义fastslow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点, 如果 fastslow指针在途中相遇,说明这个链表有环。

为什么fast走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢?
首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。
那么来看一下,为什么fast指针和slow指针一定会相遇呢?
可以画一个环,然后让fast指针在任意一个节点开始追赶slow指针。

这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。

/*** Definition for singly-linked list.* class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {public boolean hasCycle(ListNode head) {ListNode fast = head;ListNode slow = head;// 空链表、单节点链表一定不会有环while (fast != null && fast.next != null) {fast = fast.next.next; // 快指针,一次移动两步slow = slow.next;      // 慢指针,一次移动一步// 不要比较value,对象可能是个空的if (fast == slow) {   // 快慢指针相遇,表明有环return true;}}return false; // 正常走到链表末尾,表明没有环}
}

时间复杂度: O(N),其中N是链表中的节点数。当链表中不存在环时,快指针将先于慢指针到达链表尾部,链表中每个节点至多被访问两次。当链表中存在环时,每一轮移动后,快慢指针的距离将减小一。而初始距离为环的长度,因此至多移动 NNN 轮。
空间复杂度: O(1)。我们只使用了两个指针的额外空间。

三、哈希表

最容易想到的方法是遍历所有节点,每次遍历到一个节点时,判断该节点此前是否被访问过。

具体地,我们可以使用哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。

public class Solution {public boolean hasCycle(ListNode head) {Set<ListNode> seen = new HashSet<ListNode>();while (head != null) {if (!seen.add(head)) {return true;}head = head.next;}return false;}
}

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

四、数组与链表

作为线性表的两种存储方式 —— 链表和数组,这对相爱相杀的好基友有着各自的优缺点。接下来,我们梳理一下这两种方式。

数组,所有元素都连续的存储于一段内存中,且每个元素占用的内存大小相同。这使得数组具备了通过下标快速访问数据的能力。
但连续存储的缺点也很明显,增加容量,增删元素的成本很高,时间复杂度均为O(n)
增加数组容量需要先申请一块新的内存,然后复制原有的元素。如果需要的话,可能还要删除原先的内存。

删除元素时需要移动被删除元素之后的所有元素以保证所有元素是连续的。增加元素时需要移动指定位置及之后的所有元素,然后将新增元素插入到指定位置,如果容量不足的话还需要先进行扩容操作。
总结一下数组的优缺点:
优点: 可以根据偏移实现快速的随机读写。
缺点: 扩容,增删元素极慢。

链表,由若干个结点组成,每个结点包含数据域和指针域。结点结构如下图所示:
在这里插入图片描述

链表的一个结点一般来讲,链表中只会有一个结点的指针域为空,该结点为尾结点,其他结点的指针域都会存储一个结点的内存地址。链表中也只会有一个结点的内存地址没有存储在其他结点的指针域,该结点称为头结点。
在这里插入图片描述

内存中的链表链表的存储方式使得它可以高效的在指定位置插入与删除,时间复杂度均为O(1)
在结点p之后增加一个结点q总共分三步:
1、请一段内存用以存储q (可以使用内存池避免频繁申请和销毁内存)。
2、将p的指针域数据复制到q的指针域。
3、更新p的指针域为q的地址。
插入新元素

删除结点p之后的结点q总共分两步:
1、将q的指针域复制到p的指针域。
2、释放q结点的内存。
在这里插入图片描述

#include <bits/stdc++.h>using namespace std;//定义一个结点模板template<typename T>struct Node {T data;Node *next;Node() : next(nullptr) {}Node(const T &d) : data(d), next(nullptr) {}};//删除 p 结点后面的元素template<typename T>void Remove(Node<T> *p) {if (p == nullptr || p->next == nullptr) {return;}auto tmp = p->next->next;delete p->next;p->next = tmp;}//在 p 结点后面插入元素template<typename T>void Insert(Node<T> *p, const T &data) {auto tmp = new Node<T>(data);tmp->next = p->next;p->next = tmp;}//遍历链表template<typename T, typename V>void Walk(Node<T> *p, const V &vistor) {while(p != nullptr) {vistor(p);p = p->next;}}int main() {auto p = new Node<int>(1);Insert(p, 2);int sum = 0;Walk(p, [&sum](const Node<int> *p) -> void { sum += p->data; });cout << sum << endl;Remove(p);sum = 0;Walk(p, [&sum](const Node<int> *p) -> void { sum += p->data; });cout << sum << endl;return 0;}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPS - 表格虚线变成实线解决方案(Office 同上)

1、选中表格区域&#xff0c;在表格中选中需要调整为实线的表格区域 2、点击设置单元格格式&#xff0c;鼠标进行右击并点击设置单元格格式选项 3、选择实线&#xff0c;在单元格格式下的边框&#xff0c;调整到实线 4、设置为实线&#xff0c;即可将表格的虚线设置为实线

Android Studio 实现网易新闻App (简单方便易懂)

&#x1f345;文章末尾有获取完整项目源码方式&#x1f345; 目录 前言 一、任务介绍 1.1 背景 1.2目的和意义 二、 实现介绍 视频演示 2.1 启动页实现 2.2 注册页面实现 2.3 登陆页面实现 2.4 首页实现 2.5 详情页面实现 三、获取源码 前言 随着移动互联网的持续发…

py的函数讲解

前言:本章节我们来讲函数&#xff0c;主播略微感觉到有点小难&#xff0c;友友们需要认真看 目录 一.初始函数 1.1关于函数 1.2举例 1.3小结 二.函数的基础语法 2.1关于函数的语法 2.2举例 2.3小结 三.函数的参数 3.1关于函数的参数 3.2举例 3.3小结 四.函数的返回…

数据结构 模拟实现二叉树(孩子表示法)

目录 一、二叉树的简单概念 &#xff08;1&#xff09;关于树的一些概念 &#xff08;2&#xff09;二叉树的一些概念及性质 定义二叉树的代码&#xff1a; 二、二叉树的方法实现 &#xff08;1&#xff09;createTree &#xff08;2&#xff09;preOrder &#xff08;…

BikeDNA(四)初始化参考数据

BikeDNA&#xff08;四&#xff09;初始化参考数据 这本笔记本&#xff1a; 加载定义研究区域的多边形&#xff0c;然后为研究区域创建网格叠加。加载参考数据。处理参考数据以创建分析所需的网络结构和属性。 先决条件和条件 输入/输出 config.yml 必须提前设置。 此笔记本…

谷歌aab包在Android 14闪退而apk没问题(targetsdk 34)

问题原因 Unity应用(target SDK 34)上线到GooglePlay&#xff0c;有用户反馈fold5设备上&#xff08;Android14系统&#xff09;疯狂闪退&#xff0c;经测试&#xff0c;在小米手机Android14系统的版本复现成功了&#xff0c;奇怪的是apk直接安装没问题&#xff0c;而打包成aa…

【算法】信使(最短路问题)

题目 战争时期&#xff0c;前线有 n 个哨所&#xff0c;每个哨所可能会与其他若干个哨所之间有通信联系。 信使负责在哨所之间传递信息&#xff0c;当然&#xff0c;这是要花费一定时间的&#xff08;以天为单位&#xff09;。 指挥部设在第一个哨所。 当指挥部下达一个命令…

详细分析Java中的@Transactional注解

目录 前言1. 基本知识2. 常用属性3. Demo4. 总结 前言 Transactional 是 Spring 框架中用于管理事务的注解。 该注解来源于Spring&#xff0c;对于Spring的基础知识可看我之前的文章&#xff1a; Spring框架从入门到学精&#xff08;全&#xff09; 该注解也可用在xxl-job框架…

redis夯实之路-哨兵(Sentinel)机制详解

Sentinel&#xff08;哨兵&#xff09;保证了redis的高可用性&#xff0c;一个Sentinel或多个Sentinel组成的系统监视多个主从服务器&#xff0c;当主服务器下线时&#xff0c;自动将一个从服务器升级为主服务器。 sentinel的主要功能 集群监控&#xff1a;负责监控redis mas…

Ubuntu root 远程登录失败

背景&#xff1a;设置了两个系统用户&#xff1a;root、test&#xff1b;test可以登录&#xff0c;可以使用su 命令切换root用户登录成功&#xff1b; 但是直接用root登录&#xff0c;会报错。 查看登录日志的方法&#xff1a; 需要两个远程窗口&#xff0c;在第一个远程窗口…

Komodor:Kubernetes 监控工具全面指南

为了方便起见&#xff0c;Komodor 提供了一个简单的 Web 界面&#xff0c;以帮助您监控 Kubernetes 集群的状态。它拥有付费和免费增值计划&#xff0c;除了在出现问题时通知用户外&#xff0c;还拥有一系列方便的工具&#xff0c;用于跟踪和管理集群中部署的资源的状态。让我们…

Smallpdf扫描、转换、压缩、编辑、签名PDF

【应用名称】&#xff1a;Smallpdf: 扫描、转换、压缩、编辑、签名PDF 【适用平台】&#xff1a;#Android 【软件标签】&#xff1a;#Smallpdf 【应用版本】&#xff1a;1.71.0 【应用大小】&#xff1a;150MB 【软件说明】&#xff1a;通过 Smallpdf&#xff0c;您可以&…