160. 相交链表

devtools/2024/9/23 8:43:49/

问题描述

给定两个单链表的头节点 headAheadB,找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,则返回 null

注意

  • 返回结果后,链表必须保持其原始结构。

提示

  • listA 中节点数目为 m
  • listB 中节点数目为 n
  • 1 <= m, n <= 30,000
  • 1 <= Node.val <= 100,000
  • 0 <= skipA <= m
  • 0 <= skipB <= n
  • 如果 listAlistB 没有交点,intersectVal 为 0
  • 如果 listAlistB 有交点,intersectVal == listA[skipA] == listB[skipB]

进阶

设计一个时间复杂度为 O(m + n) 且仅用 O(1) 内存的解决方案。

代码

/*** Definition for singly-linked list.* struct ListNode {*     int val;*     struct ListNode *next;* };*/
#include <stdlib.h>
struct ListNode {int val;struct ListNode *next;
};
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {struct ListNode* dummyA = headA;struct ListNode* dummyB = headB;while (dummyA != NULL){while (dummyB != NULL){if(dummyA == dummyB){return dummyB;}if(dummyB->next != NULL){dummyB = dummyB->next;}else{dummyB = headB;break;}}if(dummyA->next != NULL){dummyA = dummyA->next;}else{dummyA = NULL;}}return NULL;
}

代码思路分析

该代码试图通过嵌套循环遍历两个链表,找到它们的相交节点。如果找到了相交节点,则返回该节点;如果没有找到,则返回 NULL

分块拆解分析

定义结构和初始化
#include <stdlib.h>
struct ListNode {int val;struct ListNode *next;
};
  • 定义了一个单链表节点的结构体 ListNode,包含一个整数值 val 和一个指向下一个节点的指针 next
核心函数 getIntersectionNode
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {struct ListNode* dummyA = headA;struct ListNode* dummyB = headB;
  • 初始化两个指针 dummyAdummyB,分别指向链表 headAheadB 的头节点。
外层循环遍历 dummyA
    while (dummyA != NULL){
  • 遍历链表 headA,直到 dummyA 变为 NULL
内层循环遍历 dummyB
        while (dummyB != NULL){
  • 遍历链表 headB,直到 dummyB 变为 NULL
检查相交节点
            if(dummyA == dummyB){return dummyB;}
  • 如果 dummyAdummyB 指向同一个节点,则返回该节点,即找到了相交点。
移动 dummyB 指针
            if(dummyB->next != NULL){dummyB = dummyB->next;}else{dummyB = headB;break;}
  • 如果 dummyB 的下一个节点不为 NULL,则移动到下一个节点。
  • 如果 dummyB 的下一个节点为 NULL,则重置 dummyB 指针为 headB,并跳出内层循环。
移动 dummyA 指针
        if(dummyA->next != NULL){dummyA = dummyA->next;}else{dummyA = NULL;}}return NULL;
}
  • 如果 dummyA 的下一个节点不为 NULL,则移动到下一个节点。
  • 如果 dummyA 的下一个节点为 NULL,则将 dummyA 置为 NULL,结束外层循环。
  • 如果两个链表没有相交节点,则返回 NULL

复杂度分析

时间复杂度

  • 最坏情况下,外层循环遍历 headA 的每个节点,内层循环遍历 headB 的每个节点。因此时间复杂度为 O(m * n),其中 mn 分别是链表 headAheadB 的长度。

空间复杂度

  • 该算法只使用了常数级的额外空间,因此空间复杂度为 O(1)

改进

上述代码的时间复杂度较高,可以通过双指针法优化,将时间复杂度降低到 O(m + n)。具体思路是:

  1. 初始化两个指针 pApB,分别指向 headAheadB
  2. 遍历两个链表,当 pA 到达末尾时,重置为 headB;当 pB 到达末尾时,重置为 headA
  3. 两个指针最终会相遇于相交节点,或同时到达链表末尾(无相交节点)。

优化后的代码实现

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;
}

会相交的原因

双指针法能够找到链表相交的节点,尽管两个链表的前置节点步长不一样,这是由于指针在遍历完一个链表后切换到另一个链表,从而消除了长度差异,使两个指针最终同步到达相交节点。以下是详细解释:

详细解释

假设链表 headA 的长度为 m链表 headB 的长度为 n。为了简化说明,假设 headAheadB 在节点 C 相交。

  • 链表 headAA1 -> A2 -> ... -> Am -> C -> ...
  • 链表 headBB1 -> B2 -> ... -> Bn -> C -> ...

如果 mn 不相等,那么指针 pApB 在初始遍历时到达末尾的时间不同。但通过交替切换遍历,两个指针在相同的时间步到达相交节点。下面是具体过程:

  1. 初始化两个指针 pApB,分别指向 headAheadB
  2. 让两个指针分别遍历各自的链表
  3. 当指针到达链表末尾时,切换到另一个链表的头部继续遍历。
  4. 最终,两个指针会在相交节点相遇,或者同时到达链表末尾(无相交节点)。

双指针遍历过程

pA的轨迹
  • pAheadA 开始,走 m + 1 + c_to_end 步到达末尾,然后切换到 headB
  • pA 继续遍历 headB,走 n 步到达C。
pB的轨迹
  • pBheadB 开始,走 n + 1 + c_to_end 步到达末尾,然后切换到 headA
  • pB 继续遍历 headA,走 m 步到达C。

由于 pApB 走的总步数相等(都是 m + n + 1 + c_to_end 步),如果m == n,则在第一次遍历的时候就会相遇在C的位置,否则在遍历完两个链表的节点后,两个指针会相遇于相交节点 C,或者同时到达链表末尾(无相交节点)。

时间复杂度和空间复杂度

  • 时间复杂度O(m + n),因为每个指针最多遍历两个链表的总长度。
  • 空间复杂度O(1),只使用了常数级的额外空间。

这种方法通过交替遍历链表,实现了在相同的时间步内找到两个链表的相交节点或确认无相交节点,避免了嵌套循环的高时间复杂度。

结果

源代码结果如下:
优化前
优化后结果如下:

优化后


http://www.ppmy.cn/devtools/46256.html

相关文章

YOLOv9改进策略 | 添加注意力篇 | 利用YOLOv10提出的PSA注意力机制助力YOLOv9有效涨点(附代码 + 详细修改教程)

一、本文介绍 本文给大家带来的改进机制是YOLOv10提出的PSA注意力机制&#xff0c;自注意力在各种视觉任务中得到了广泛应用&#xff0c;因为它具有显著的全局建模能力。然而&#xff0c;自注意力机制表现出较高的计算复杂度和内存占用。为了解决这个问题&#xff0c;鉴于注意…

【Python】使用 Python 查询域名的 IP 地址

我们都已经长大 好多梦正在飞 就像童年看到的 红色的蜻蜓 我们都已经长大 好多梦还要飞 就像现在心目中 红色的蜻蜓 &#x1f3b5; 小虎队《红蜻蜓》 在网络开发和运维中&#xff0c;了解域名对应的 IP 地址是一个常见且重要的需求。Python 提供了多种方法…

安全区域边界

文章目录 安全区域边界边界防护跨边界流量通过受控接口通信非法内联非法外联限制无线网络 访问控制启用基于白名单的访问控制策略优化访问控制表根据五元组控制根据会话状态控制根据应用协议和内容控制 入侵防范外部发起的攻击内部发起的攻击对新型攻击防范及时检测攻击行为 恶…

设计模式详解(六):适配器模式——Adapter

目录导航 适配器模式及其作用现实生活举例 适配器模式的好处适配器模式的实现关系图实现步骤 适配器模式的适用场景适配器模式示例 适配器模式及其作用 适配器模式是一种结构型设计模式。所谓结构型是指在代码结构方面的设计模式。适配器模式作为中间层&#xff0c;可以让交互…

《C++primer》第九章课后习题

练习9.1 答&#xff1a;a选list&#xff0c;因为需要从容器的中间位置插入元素&#xff1b;b选vector&#xff0c;因为只需要在尾部插入元素&#xff1b;c没有最优&#xff0c;选择vector 练习9.2 list<deque<int>> l;练习9.3 答&#xff1a;指向同一个容器的…

Elasticsearch REST API 初探:索引与搜索文档的奥秘

在当今数据驱动的时代&#xff0c;高效的数据检索和存储成为了众多企业和项目的关键需求。Elasticsearch 作为一款基于 Lucene 的开源搜索和分析引擎&#xff0c;凭借其分布式、可扩展和高性能的特性&#xff0c;成为了处理大规模数据的首选工具。本文将带你初步探索 Elasticse…

Nginx企业级负载均衡:技术详解系列(14)—— 账户认证功能

你好&#xff0c;我是赵兴晨&#xff0c;97年文科程序员。 你有没有听说过Nginx的账户认证功能&#xff1f;这可不只是一个技术问题&#xff0c;它关系到我们上网时的安全和便利。就像家里需要一把钥匙才能进们一样&#xff0c;Nginx的账户认证功能就是确保有只有授权的人才能…

mysql存储地理信息的方法

MySQL 存储地理信息通常使用 GEOMETRY 数据类型或其子类型&#xff08;如 POINT, LINESTRING, POLYGON 等&#xff09;。为了支持这些数据类型&#xff0c;MySQL 提供了 SPATIAL 索引&#xff0c;这允许我们执行高效的地理空间查询。 1. 创建支持地理信息的表 首先&#xff0…