160. 相交链表

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {/*** 查找两个链表的相交节点* @param headA 第一个链表的头节点* @param headB 第二个链表的头节点* @return 相交的节点,如果不相交则返回null*/public ListNode getIntersectionNode(ListNode headA, ListNode headB) {// 使用HashSet来存储已经访问过的节点Set<ListNode> visited = new HashSet<ListNode>();// 遍历第一个链表headA,将所有节点存入HashSetListNode temp = headA;while (temp != null) {visited.add(temp);  // 将当前节点加入已访问集合temp = temp.next;   // 移动到下一个节点}// 遍历第二个链表headB,检查每个节点是否在HashSet中存在temp = headB;while (temp != null) {// 如果当前节点已存在于HashSet中,说明是相交节点if (visited.contains(temp)) {return temp;  // 返回相交节点}temp = temp.next;  // 移动到下一个节点}// 如果遍历完headB都没有找到相交节点,返回nullreturn null;}
}

代码解读:寻找两个链表的交点

这段代码实现了在Java中寻找两个单链表相交节点的功能。以下是详细解读:

方法签名

public ListNode getIntersectionNode(ListNode headA, ListNode headB)
  • 参数:两个链表的头节点 headA 和 headB

  • 返回值:两个链表相交的节点,如果不相交则返回 null

实现思路

代码使用了哈希集合的方法来解决问题,具体步骤如下:

  1. 遍历第一个链表:将链表A的所有节点存入一个HashSet中

  2. 遍历第二个链表:检查链表B的每个节点是否存在于HashSet中

  3. 找到交点:如果存在,则该节点就是交点;如果遍历完都没有找到,则返回null

代码逐行解析

Set<ListNode> visited = new HashSet<ListNode>();
  • 创建一个HashSet用于存储链表节点

ListNode temp = headA;
while (temp != null) {visited.add(temp);temp = temp.next;
}
  • 遍历链表A,将所有节点添加到HashSet中

temp = headB;
while (temp != null) {if (visited.contains(temp)) {return temp;}temp = temp.next;
}
  • 遍历链表B,检查每个节点是否存在于HashSet中

  • 如果存在,则立即返回该节点(第一个相交节点)

return null;
  • 如果遍历完链表B都没有找到相交节点,返回null

复杂度分析

  • 时间复杂度:O(m+n),其中m和n分别是两个链表的长度

  • 空间复杂度:O(m),需要存储链表A的所有节点

其他解法

这种方法虽然直观,但需要额外空间。还有两种更优的空间O(1)解法:

  1. 双指针法

    • 计算两个链表的长度

    • 让长链表的指针先移动长度差步

    • 然后两个指针同步前进,相遇点即为交点

  2. 环形检测法

    • 将链表A的尾节点连接到链表B的头节点

    • 使用快慢指针检测环的起点,即为交点

    • 最后需要恢复链表结构

边界情况

  • 两个链表都为空

  • 一个链表为空

  • 两个链表不相交

  • 两个链表完全重合

  • 交点在链表头部或尾部

206. 反转链表

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {/*** 反转单链表* @param head 原链表的头节点* @return 反转后新链表的头节点*/public ListNode reverseList(ListNode head) {// prev指针用于记录前一个节点,初始为null(因为反转后原头节点将指向null)ListNode prev = null;// curr指针用于遍历链表,初始指向原链表头节点ListNode curr = head;// 遍历链表直到当前节点为null(到达链表尾部)while (curr != null) {// 1. 先保存当前节点的下一个节点(否则修改指针后会丢失)ListNode next = curr.next;// 2. 反转指针方向:当前节点的next指向前一个节点curr.next = prev;// 3. 移动prev指针到当前节点(为下一次迭代做准备)prev = curr;// 4. 移动curr指针到之前保存的下一个节点curr = next;}// 循环结束时,prev指向原链表的最后一个节点,即新链表的头节点return prev;}
}

算法图解

假设原始链表为:1 → 2 → 3 → 4 → null

执行过程:

  1. 初始状态:prev = null, curr = 1

  2. 第一次迭代后:null ← 1 2 → 3 → 4 → null

  3. 第二次迭代后:null ← 1 ← 2 3 → 4 → null

  4. 第三次迭代后:null ← 1 ← 2 ← 3 4 → null

  5. 第四次迭代后:null ← 1 ← 2 ← 3 ← 4

最终返回节点4作为新链表的头节点

关键点说明

  1. 三指针技术

    • prev:记录前一个节点

    • curr:当前处理节点

    • next:临时保存下一个节点

  2. 指针操作顺序

    • 必须先保存next = curr.next,否则反转后会丢失后续链表

    • 然后才能修改curr.next指向

    • 最后移动prevcurr指针

  3. 终止条件

    • curr == null时循环结束

    • 此时prev指向原链表的最后一个节点(新链表的头节点)

  4. 边界情况处理

    • 空链表(head == null):直接返回null

    • 单节点链表:自动正确处理

复杂度分析

  • 时间复杂度:O(n),只需遍历链表一次

  • 空间复杂度:O(1),只使用了固定数量的指针变量

234. 回文链表

/*** Definition for singly-linked list.* public class ListNode {*     int val;*     ListNode next;*     ListNode() {}*     ListNode(int val) { this.val = val; }*     ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/
class Solution {public boolean isPalindrome(ListNode head) {ListNode temp=head;List<Integer> vals = new ArrayList<Integer>();while(temp!=null){vals.add(temp.val);temp=temp.next;}int left=0;int length=vals.size();int right=length-1;while(left<right){if(vals.get(left)!=vals.get(right)){return false;}left++;right--;}return true;}
}

算法分析

方法思路

  1. 存储阶段:将链表所有节点的值按顺序存入一个ArrayList

  2. 验证阶段:使用双指针法,从列表两端向中间比较元素是否对称

时间复杂度

  • O(n):需要完整遍历链表两次(一次存储,一次比较)

  • 其中n是链表的长度

空间复杂度

  • O(n):需要使用额外空间存储所有节点的值

141. 环形链表

/*** Definition for singly-linked list.* class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {/*** 检测链表是否有环* @param head 链表头节点* @return 如果链表有环返回true,否则返回false*/public boolean hasCycle(ListNode head) {// 处理边界情况:空链表或单节点无环if (head == null || head.next == null) {return false;}// 初始化快慢指针ListNode slow = head;      // 慢指针每次走1步ListNode fast = head.next; // 快指针每次走2步// 当快慢指针不相遇时继续循环while (slow != fast) {// 如果快指针到达链表末尾,说明无环if (fast == null || fast.next == null) {return false;}// 移动指针slow = slow.next;      // 慢指针走1步fast = fast.next.next; // 快指针走2步}// 快慢指针相遇,说明有环return true;}
}

算法原理(Floyd判圈算法)

核心思想

  • 使用两个指针,一个慢指针(每次移动1步),一个快指针(每次移动2步)

  • 如果链表无环,快指针会先到达末尾(null)

  • 如果链表有环,快指针最终会追上慢指针(相遇)

为什么这样能工作

  1. 无环情况:快指针会先到达链表末尾(null)

  2. 有环情况

    • 快指针每次比慢指针多走1步

    • 经过若干步后,快指针会绕环一圈追上慢指针

    • 可以证明最多需要O(n)时间就能检测出环

关键点解析

  1. 指针初始化

    • 慢指针slow从head开始

    • 快指针fast从head.next开始(避免第一次循环就满足slow==fast)

  2. 循环条件

    • slow != fast时继续循环

    • 如果相等则跳出循环,返回true(有环)

  3. 终止条件检查

    • 检查fast == null || fast.next == null

    • 因为fast走得快,只需要检查fast是否到末尾

  4. 指针移动

    • 慢指针每次移动1步:slow = slow.next

    • 快指针每次移动2步:fast = fast.next.next

复杂度分析

  • 时间复杂度:O(n)

    • 无环情况:快指针到达末尾,最多n/2步

    • 有环情况:慢指针最多走一圈就会被快指针追上

  • 空间复杂度:O(1)

    • 只使用了两个额外指针,常数空间

边界情况处理

  1. 空链表:直接返回false

  2. 单节点链表:直接返回false(除非自环,但题目通常不考虑)

  3. 大环/小环:算法都能正确处理

  4. 环在头部/尾部:都能正确检测

142. 环形链表 II

/*** Definition for singly-linked list.* class ListNode {*     int val;*     ListNode next;*     ListNode(int x) {*         val = x;*         next = null;*     }* }*/
public class Solution {public ListNode detectCycle(ListNode head) {if(head==null){return null;}ListNode slow = head;      // 慢指针ListNode fast = head; // 快指针while(fast!=null){slow=slow.next;if(fast.next!=null){fast=fast.next.next;}else{return null;}if(slow==fast){ListNode temp=head;while(temp!=slow){temp=temp.next;slow=slow.next;}return temp;}}return null;}
}

算法原理(Floyd判圈算法的扩展)

第一阶段:检测环的存在

  1. 快指针每次移动2步,慢指针每次移动1步

  2. 如果快指针遇到null,说明链表无环

  3. 如果快慢指针相遇,说明链表有环

第二阶段:确定环的起点

  1. 当快慢指针相遇后,将一个指针(temp)重新指向链表头部

  2. temp和slow指针每次都移动1步

  3. 当它们再次相遇时,相遇点就是环的起点

数学证明

设:

  • 链表头到环起点的距离为a

  • 环起点到相遇点的距离为b

  • 相遇点回到环起点的距离为c

  • 环的长度为L = b + c

当快慢指针相遇时:

  • 慢指针走的距离:a + b

  • 快指针走的距离:a + b + k*L (k为快指针绕环的圈数)

因为快指针速度是慢指针的2倍:
2(a + b) = a + b + kL
=> a + b = k
L
=> a = k*L - b = (k-1)*L + c

这意味着:从链表头走a步,等于从相遇点走c步加上整数倍的环长。因此,两个指针分别从头节点和相遇点出发,以相同速度前进,必将在环起点相遇。

复杂度分析

  • 时间复杂度:O(n)

    • 第一阶段最多需要O(n)时间检测环

    • 第二阶段最多需要O(n)时间找到环起点

  • 空间复杂度:O(1)

    • 只使用了固定数量的指针变量

边界情况处理

  1. 空链表:直接返回null

  2. 单节点无环:快指针会走到null

  3. 单节点自环:能正确检测并返回该节点

  4. 环在头部:能正确返回头节点

  5. 环在尾部:能正确返回环起点

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

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

相关文章

LLM 中 语音编码与文本embeding的本质区别

直接使用语音编码,是什么形式,和文本的区别 直接使用语音编码的形式 语音编码是将模拟语音信号转换为数字信号的技术,其核心是对语音的声学特征进行数字化表征,直接承载语音的物理声学信息。其形式可分为以下几类: 1. 基于波形的编码(保留原始波形特征) 脉冲编码调制…

模型选择与调优

一、模型选择与调优在机器学习中&#xff0c;模型的选择和调优是一个重要的步骤&#xff0c;它直接影响到最终模型的性能1、交叉验证在任何有监督机器学习项目的模型构建阶段&#xff0c;我们训练模型的目的是从标记的示例中学习所有权重和偏差的最佳值如果我们使用相同的标记示…

vue+Django农产品推荐与价格预测系统、双推荐+机器学习预测+知识图谱

vueflask农产品推荐与价格预测系统、双推荐机器学习价格预测知识图谱文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站&#xff0c;有好处&#xff01;编号: D010 技术架构: vueflaskmysqlneo4j 核心技术&#xff1a; 基…

数据分析小白训练营:基于python编程语言的Numpy库介绍(第三方库)(下篇)

衔接上篇文章&#xff1a;数据分析小白训练营&#xff1a;基于python编程语言的Numpy库介绍&#xff08;第三方库&#xff09;&#xff08;上篇&#xff09;&#xff08;十一&#xff09;数组的组合核心功能&#xff1a;一、生成基数组np.arange().reshape() 基础运算功能&…

负载因子(Load Factor) :哈希表(Hash Table)中的一个关键性能指标

负载因子&#xff08;Load Factor&#xff09; 是哈希表&#xff08;Hash Table&#xff09;中的一个关键性能指标&#xff0c;用于衡量哈希表的空间利用率和发生哈希冲突的可能性。一&#xff1a;定义负载因子&#xff08;通常用希腊字母 λ 表示&#xff09;的计算公式为&…

监控插件SkyWalking(一)原理

一、介绍 1、简介 SkyWalking 是一个 开源的 APM&#xff08;Application Performance Monitoring&#xff0c;应用性能监控&#xff09;和分布式追踪系统&#xff0c;主要用于监控、追踪、分析分布式系统中的调用链路、性能指标和日志。 它由 Apache 基金会托管&#xff0c;…

【接口自动化测试】---自动化框架pytest

目录 1、用例运行规则 2、pytest命令参数 3、pytest配置文件 4、前后置 5、断言 6、参数化---对函数的参数&#xff08;重要&#xff09; 7、fixture 7.1、基本用法 7.2、fixture嵌套&#xff1a; 7.3、请求多个fixture&#xff1a; 7.4、yield fixture 7.5、带参数…

Flink Stream API 源码走读 - socketTextStream

概述 本文深入分析了 Flink 中 socketTextStream() 方法的源码实现&#xff0c;从用户API调用到最终返回 DataStream 的完整流程。 核心知识点 1. socketTextStream 方法重载链 // 用户调用入口 env.socketTextStream("hostname", 9999)↓ 补充分隔符参数 env.socket…

待办事项小程序开发

1. 项目规划功能需求&#xff1a;添加待办事项标记完成/未完成删除待办事项分类或标签管理&#xff08;可选&#xff09;数据持久化&#xff08;本地存储&#xff09;2. 实现功能添加待办事项&#xff1a;监听输入框和按钮事件&#xff0c;将输入内容添加到列表。 标记完成/未完…

【C#】Region、Exclude的用法

在 C# 中&#xff0c;Region 和 Exclude 是与图形编程相关的概念&#xff0c;通常在使用 System.Drawing 命名空间进行 GDI 绘图时出现。它们主要用于定义和操作二维空间中的区域&#xff08;几何区域&#xff09;&#xff0c;常用于窗体裁剪、控件重绘、图形绘制优化等场景。 …

机器学习 - Kaggle项目实践(3)Digit Recognizer 手写数字识别

Digit Recognizer | Kaggle 题面 Digit Recognizer-CNN | Kaggle 下面代码的kaggle版本 使用CNN进行手写数字识别 学习到了网络搭建手法学习率退火数据增广 提高训练效果。 使用混淆矩阵 以及对分类出错概率最大的例子单独拎出来分析。 最终以99.546%正确率 排在 86/1035 …

新手如何高效运营亚马逊跨境电商:从传统SP广告到DeepBI智能策略

"为什么我的广告点击量很高但订单转化率却很低&#xff1f;""如何避免新品期广告预算被大词消耗殆尽&#xff1f;""为什么手动调整关键词和出价总是慢市场半拍&#xff1f;""竞品ASIN投放到底该怎么做才有效&#xff1f;""有没有…

【论文阅读 | CVPR 2024 | UniRGB-IR:通过适配器调优实现可见光-红外语义任务的统一框架】

论文阅读 | CVPR 2024 | UniRGB-IR&#xff1a;通过适配器调优实现可见光-红外语义任务的统一框架​1&&2. 摘要&&引言3.方法3.1 整体架构3.2 多模态特征池3.3 补充特征注入器3.4 适配器调优范式4 实验4.1 RGB-IR 目标检测4.2 RGB-IR 语义分割4.3 RGB-IR 显著目…

Hyperf 百度翻译接口实现方案

保留 HTML/XML 标签结构&#xff0c;仅翻译文本内容&#xff0c;避免破坏富文本格式。采用「HTML 解析 → 文本提取 → 批量翻译 → 回填」的流程。百度翻译集成方案&#xff1a;富文本内容翻译系统 HTML 解析 百度翻译 API 集成 文件结构 app/ ├── Controller/ │ └──…

字节跳动 VeOmni 框架开源:统一多模态训练效率飞跃!

资料来源&#xff1a;火山引擎-开发者社区 多模态时代的训练痛点&#xff0c;终于有了“特效药” 当大模型从单一语言向文本 图像 视频的多模态进化时&#xff0c;算法工程师们的训练流程却陷入了 “碎片化困境”&#xff1a; 当业务要同时迭代 DiT、LLM 与 VLM时&#xff0…

配置docker pull走http代理

之前写了一篇自建Docker镜像加速器服务的博客&#xff0c;需要用到境外服务器作为代理&#xff0c;但是一般可能没有境外服务器&#xff0c;只有http代理&#xff0c;所以如果本地使用想走代理可以用以下方式 临时生效&#xff08;只对当前终端有效&#xff09; 设置环境变量…

OpenAI 开源模型 gpt-oss 本地部署详细教程

OpenAI 最近发布了其首个开源的开放权重模型gpt-oss&#xff0c;这在AI圈引起了巨大的轰动。对于广大开发者和AI爱好者来说&#xff0c;这意味着我们终于可以在自己的机器上&#xff0c;完全本地化地运行和探索这款强大的模型了。 本教程将一步一步指导你如何在Windows和Linux…

力扣-5.最长回文子串

题目链接 5.最长回文子串 class Solution {public String longestPalindrome(String s) {boolean[][] dp new boolean[s.length()][s.length()];int maxLen 0;String str s.substring(0, 1);for (int i 0; i < s.length(); i) {dp[i][i] true;}for (int len 2; len …

Apache Ignite超时管理核心组件解析

这是一个非常关键且设计精巧的 定时任务与超时管理组件 —— GridTimeoutProcessor&#xff0c;它是 Apache Ignite 内核中负责 统一调度和处理所有异步超时事件的核心模块。&#x1f3af; 一、核心职责统一管理所有需要“在某个时间点触发”的任务或超时逻辑。它相当于 Ignite…

DAY 42 Grad-CAM与Hook函数

知识点回顾回调函数lambda函数hook函数的模块钩子和张量钩子Grad-CAM的示例# 定义一个存储梯度的列表 conv_gradients []# 定义反向钩子函数 def backward_hook(module, grad_input, grad_output):# 模块&#xff1a;当前应用钩子的模块# grad_input&#xff1a;模块输入的梯度…