160. 相交链表 - 题解与详细分析

160. 相交链表 - 题解与详细分析

题目描述

给你两个单链表的头节点 headA 和 headB,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

图示两个链表在节点 c1 开始相交:

text

A: a1 → a2 ↘ c1 → c2 → c3 ↗ B: b1 → b2 → b3

注意:

  • 题目数据保证整个链式结构中不存在环
  • 函数返回结果后,链表必须保持其原始结构

解题思路

这道题要求找到两个链表的相交节点。由于链表可能长度不同,但相交后的部分长度相同,我们可以通过以下方法解决:

核心思想

  1. 计算链表长度:分别遍历两个链表,得到它们的长度
  2. 对齐起点:让长链表先移动长度差值的步数,使两个链表的剩余长度相等
  3. 同时遍历:两个指针同时移动,比较节点是否相同

为什么这样有效?

  • 如果两个链表相交,那么从相交点到链表末尾的长度是相同的
  • 通过让长链表先走差值步,两个指针会同时到达相交点(如果存在)

代码实现

c

/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) { struct ListNode *cur1 = headA; struct ListNode *cur2 = headB; int a = 0; int b = 0; // 计算链表A的长度 while(cur1) { a++; cur1 = cur1->next; } // 计算链表B的长度 while(cur2) { b++; cur2 = cur2->next; } // 重置指针到链表头部 cur1 = headA; cur2 = headB; int c = 0; // 让长链表先移动长度差值的步数 if(a > b) { c = a - b; while(c--) { cur1 = cur1->next; } } else { c = b - a; while(c--) { cur2 = cur2->next; } } // 同时遍历两个链表,寻找相交节点 while(cur1) { if(cur1 == cur2) { return cur1; } cur1 = cur1->next; cur2 = cur2->next; } return NULL; }

代码详解

第一部分:计算链表长度

c

struct ListNode *cur1 = headA; struct ListNode *cur2 = headB; int a = 0; int b = 0; // 计算链表A的长度 while(cur1) { a++; cur1 = cur1->next; } // 计算链表B的长度 while(cur2) { b++; cur2 = cur2->next; }

  • 使用两个指针分别遍历两个链表
  • 计数器 a 和 b 分别记录链表A和链表B的长度

第二部分:对齐起点

c

cur1 = headA; cur2 = headB; int c = 0; if(a > b) { c = a - b; while(c--) { cur1 = cur1->next; } } else { c = b - a; while(c--) { cur2 = cur2->next; } }

  • 重置指针到链表头部
  • 计算长度差值 c
  • 让长链表的指针先移动 c 步,使两个指针后的剩余长度相等

第三部分:寻找相交节点

c

while(cur1) { if(cur1 == cur2) { return cur1; } cur1 = cur1->next; cur2 = cur2->next; } return NULL;

  • 两个指针同时移动
  • 比较节点地址是否相同(注意:是比较节点本身,不是节点值)
  • 找到相同节点则返回,否则返回 NULL

执行过程可视化

以题目示例为例:

text

链表A: a1 → a2 → c1 → c2 → c3 链表B: b1 → b2 → b3 → c1 → c2 → c3

执行步骤:

  1. 计算长度:
    • 链表A长度:5
    • 链表B长度:6
  2. 对齐起点:
    • 长度差:1
    • 链表B指针先移动1步:b2 → b3 → c1 → c2 → c3
  3. 同时遍历:
    • A: a1 → a2 → c1 → c2 → c3
    • B: b2 → b3 → c1 → c2 → c3
    • 在节点c1处相遇,返回c1

优化方案:双指针法

还有一种更巧妙的方法,不需要计算链表长度:

c

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) { if (headA == NULL || headB == NULL) return NULL; struct ListNode *pA = headA; struct ListNode *pB = headB; while (pA != pB) { pA = pA == NULL ? headB : pA->next; pB = pB == NULL ? headA : pB->next; } return pA; }

原理:

  • 两个指针分别遍历两个链表
  • 当指针到达链表末尾时,切换到另一个链表的头部
  • 如果两个链表相交,指针会在相交点相遇
  • 如果不相交,指针会同时到达NULL

复杂度分析

长度差法

  • 时间复杂度:O(m+n),其中m和n分别是两个链表的长度
  • 空间复杂度:O(1),只使用常数级别的额外空间

双指针法

  • 时间复杂度:O(m+n)
  • 空间复杂度:O(1)

关键点总结

  1. 比较节点地址:注意是比较节点本身(指针地址),不是节点值
  2. 链表无环:题目保证链表无环,简化了问题
  3. 保持原始结构:算法不应该修改链表结构
  4. 边界情况:处理空链表的情况

测试用例考虑

  1. 不相交的链表:返回NULL
  2. 完全相同的链表:返回头节点
  3. 一个链表是另一个的子集:返回较长链表的头部
  4. 在中间节点相交:返回相交节点
  5. 空链表:返回NULL

应用场景

这种链表相交问题在实际开发中有多种应用:

  1. 内存管理:检测两个数据结构是否共享内存区域
  2. 图算法:在树或图中寻找公共节点
  3. 版本控制系统:寻找代码分支的合并点
  4. 数据库查询:优化连接查询的执行计划

总结

寻找相交链表节点是一个经典的链表问题,掌握两种解法都很重要:

  1. 长度差法:思路直观,容易理解和实现
  2. 双指针法:代码简洁,不需要计算长度

关键技巧:

  • 利用相交后部分长度相同的特性
  • 通过对齐起点来简化比较过程
  • 注意指针操作和边界条件处理

这道题考察了对链表结构的理解和双指针技巧的应用,是面试中常见的问题之一。

Read more

某东APP端sslpning抓包+ep,cipher,sign等魔改算法+so层解析

某东APP端sslpning抓包+ep,cipher,sign等魔改算法+so层解析

本文仅用于逆向研究交流,禁止非法用途,如遇侵权联系删除!!! 准备 某购物APP 15.2.80版本 通过豌豆荚下载 aHR0cHM6Ly93d3cud2FuZG91amlhLmNvbS9hcHBzLzI3OTk4Ny9oaXN0b3J5X3YxMDE3NjE= 设备mi6 抓包工具reqable APP抓包 这个app直接抓是抓不到的因为有ssl pinning 这里推荐的方案是 Lsposed+JustTrustMe 过掉ssl pinning 接口分析 接口位置为首页下拉 关键词搜下面 首先看参数 再看请求体 有很多都是加密 我们本篇就先分析这几个 body和ep中都很像base64加密但是我测试了一下都不是  估计是魔改的算法 sign像是md5 st时间戳 sv暂时不知道 魔改base64分析 把下载的安装包拖进去进行jadx反编译分析 反编译之后搜索ep里面关键字cipher 可以看到这里把参数put进去的我们看看JSONObject是怎么来的 定位到了生存位置我们分析下JSONObject生成流程

By Ne0inhk
从零开始打造高性能数据结构——手把手教你实现环形缓冲

从零开始打造高性能数据结构——手把手教你实现环形缓冲

◆ 博主名称: 小此方-ZEEKLOG博客 大家好,欢迎来到小此方的博客。 ⭐️个人专栏:《C语言》_小此方的博客-ZEEKLOG博客 算法_小此方的博客-ZEEKLOG博客  ⭐️踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰。 目录 一,普通队列的劣势 1. 空间浪费严重(“假溢出”问题) 2. 需要频繁移动元素(若避免浪费) 3. 扩容成本高 4. 无法解决“假溢出”导致的提前扩容 二,环形缓冲结构分析  1. “循环”取模实现指针回绕  2.“循环”,轮流入座而不是排长队 三,实现环形缓冲 1,MyCircularQueue(k): 构造器   1,结构体搭建   2,初始化 3,为什么选择k+1块空间而不是k块空间?

By Ne0inhk

优选算法——前缀和

👇作者其它专栏 《数据结构与算法》《算法》《C++起始之路》 前缀和相关题解 1.前缀和 算法思路: a.先预处理出来一个【前缀和】数组:         用dp[i]表示:[1,i]区间内所有元素的和,那么dp[i-1]里面存的就是[1,i-1]区间内所有元素的和,那么:可得到递推公式:dp[i]=dp[i-1]+arr[i]; b.使用前缀和数组,【快速】求出【某一个区间内】所有元素的和:         当访问的区间是[l,r]时:区间内所有元素的和为:dp[r]-dp[l-r]。 #include <

By Ne0inhk
蓝桥杯C++组算法知识点整理 · 考前突击(上)【小白适用】

蓝桥杯C++组算法知识点整理 · 考前突击(上)【小白适用】

【背景说明】本文的作者是一名算法竞赛小白,在第一次参加蓝桥杯之前希望整理一下自己会了哪些算法,于是有了本文的诞生。分享在这里也希望与众多学子共勉。如果时间允许的话,这一系列会分为上中下三部分和大家见面,祝大家竞赛顺利! 【文风说明】本文主要会用代码+注释的方式来解释内容。相信学过编程的人都会发现程序比长篇大论更易理解! 目录 一、语言基础 1.1 编程基础 1.2 竞赛常用库函数 1.2.1 sort 函数 1.2.2 最值查找 1.2.3 二分查找 1.2.4 大小写转换 1.2.5 全排列 1.2.6 其它库函数整理 1.3 STL的用法 1.

By Ne0inhk