【LeetCode Hot 100 刷题日记(22/100)】160. 相交链表——链表、双指针、哈希表📌

📌 题目链接:160. 相交链表 - 力扣(LeetCode)

🔍 难度:简单 | 🏷️ 标签:链表、双指针、哈希表

⏱️ 目标时间复杂度:O(m + n)

💾 空间复杂度:O(1)(最优解)

本篇核心算法双指针技巧(Two Pointers)

🎯 面试重点:如何在 O(1) 空间下解决链表相交问题?为什么双指针能"对齐"路径长度?

🔥 适用场景:链表结构对比、路径匹配、环检测的前置思维训练


🧩 题目分析

给定两个单链表 headAheadB,要求找出它们相交的起始节点 。若无交点,则返回 null

📌 关键信息:

  • 不存在环(保证链表是线性的)
  • 节点值可能重复,但内存地址相同才表示相交
  • 必须保持原始结构(不能修改链表)

💡 示例中强调:值为 1 的节点不相交,因为它们在内存中是不同对象!只有当两个指针指向同一个节点时才算相交!

❗️ 注意:不是"值相同"就是相交,而是"引用相同"才是相交!


🔍 核心算法及代码讲解

✅ 方法一:哈希集合(Hash Set)------空间换时间

cpp 复制代码
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode*> visited;
        ListNode* temp = headA;
        while (temp != nullptr) {
            visited.insert(temp); // 将 headA 中所有节点存入哈希表
            temp = temp->next;
        }
        temp = headB;
        while (temp != nullptr) {
            if (visited.count(temp)) { // 若 headB 中某节点已在哈希表中,说明找到交点
                return temp;
            }
            temp = temp->next;
        }
        return nullptr; // 未找到交点
    }
};

🧠 原理说明:

  • 使用 unordered_set<ListNode*> 存储 headA 所有节点的指针(地址)
  • 遍历 headB,一旦发现某个节点已存在于哈希表中 → 即为交点
  • 第一个匹配的节点即为最早相交的节点

📈 复杂度分析:

项目 分析
时间复杂度 O(m + n),遍历两个链表各一次
空间复杂度 O(m),存储 headA 的全部节点

❌ 缺点:使用了额外空间,不符合进阶要求(O(1) 空间)


✅ 方法二:双指针法(Two Pointers)------最优解!

这是本题最经典的解法,也是面试官最爱考察的「巧妙思维」。

cpp 复制代码
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == nullptr || headB == nullptr) {
            return nullptr; // 至少有一个为空,则不可能相交
        }

        ListNode* pA = headA; // 指针 A,初始指向 headA
        ListNode* pB = headB; // 指针 B,初始指向 headB

        while (pA != pB) { // 当两个指针不相遇时继续循环
            pA = pA == nullptr ? headB : pA->next; // pA 到尾部则跳转到 headB
            pB = pB == nullptr ? headA : pB->next; // pB 到尾部则跳转到 headA
        }

        return pA; // 此时 pA == pB,可能是交点或都为 null
    }
};

🛠️ 行注释详解:

cpp 复制代码
ListNode* pA = headA; // 指针从 headA 开始
ListNode* pB = headB; // 指针从 headB 开始

while (pA != pB) { // 只要没相遇就继续走
    pA = pA == nullptr ? headB : pA->next; // 如果 pA 走完 headA,就跳到 headB 继续走
    pB = pB == nullptr ? headA : pB->next; // 如果 pB 走完 headB,就跳到 headA 继续走
}
return pA; // 最终要么是交点,要么都是 null(不相交)

🔍 为什么双指针能工作?------数学证明

设:

  • headA 长度 = m = a + c
  • headB 长度 = n = b + c
  • 其中 a: headA 不相交部分长度,b: headB 不相交部分长度,c: 相交部分长度

🔄 运行过程模拟:

步骤 pA 路径 pB 路径
1 headA → ... → tailA headB → ... → tailB
2 headB → ... → 交点 headA → ... → 交点

👉 总共走了多少步?

  • pA 走了:a + c + b
  • pB 走了:b + c + a

✅ 两者总路程相同!因此会在同一时间到达交点

🎯 关键洞察:通过"绕路",让两个指针走过相同的总长度,从而自然对齐!

✅ 特殊情况处理:

  • 若两链表不相交 → pA 和 pB 同时变为 nullptr → 返回 null
  • 若相交 → 在交点处相遇 → 返回该节点

🧭 解题思路(分步拆解)

  1. 边界判断

    • 若任一链表为空 → 不可能相交 → 返回 nullptr
  2. 初始化双指针

    • pA = headA, pB = headB
  3. 同步移动指针

    • 每次移动一步,若当前指针为 nullptr,则跳转到另一个链表的头节点
  4. 判断是否相遇

    • pA == pB 时停止循环
    • 返回当前节点(可能是交点或 null
  5. 逻辑闭环

    • 不相交 → 两个指针都会走到 null 并同时退出
    • 相交 → 在交点处相遇并返回

📊 算法分析

指标 哈希表法 双指针法
时间复杂度 O(m + n) O(m + n)
空间复杂度 O(m) O(1) ✅
是否破坏结构
面试推荐度 ⭐⭐ ⭐⭐⭐⭐⭐
思维难度 ⭐⭐ ⭐⭐⭐

💡 面试加分项:你能说出"双指针法的本质是让两个指针走相同的总距离"吗?这正是体现你理解深度的关键!


💻 代码(完整可运行版本)

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

// Definition for singly-linked list.
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

// 方法一:哈希表(空间 O(m))
class Solution_Hash {
public:
    ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
        unordered_set<ListNode*> visited;
        ListNode* temp = headA;
        while (temp != nullptr) {
            visited.insert(temp);
            temp = temp->next;
        }
        temp = headB;
        while (temp != nullptr) {
            if (visited.count(temp)) {
                return temp;
            }
            temp = temp->next;
        }
        return nullptr;
    }
};

// 方法二:双指针(空间 O(1))✅ 推荐
class Solution {
public:
    ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
        if (headA == nullptr || headB == nullptr) {
            return nullptr;
        }

        ListNode* pA = headA;
        ListNode* pB = headB;

        while (pA != pB) {
            pA = pA == nullptr ? headB : pA->next;
            pB = pB == nullptr ? headA : pB->next;
        }

        return pA;
    }
};

// 测试函数
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    // 构造测试用例 1:[4,1,8,4,5] 和 [5,6,1,8,4,5],交点为 8
    ListNode* headA = new ListNode(4);
    headA->next = new ListNode(1);
    headA->next->next = new ListNode(8);
    headA->next->next->next = new ListNode(4);
    headA->next->next->next->next = new ListNode(5);

    ListNode* headB = new ListNode(5);
    headB->next = new ListNode(6);
    headB->next->next = new ListNode(1);
    headB->next->next->next = headA->next->next; // 指向交点 8

    Solution sol;
    ListNode* result = sol.getIntersectionNode(headA, headB);

    if (result) {
        cout << "Intersected at '" << result->val << "'" << endl;
    } else {
        cout << "No intersection" << endl;
    }

    // 清理内存(实际面试中可忽略)
    // ...

    return 0;
}

🧪 测试用例 & 结果

输入 输出 说明
listA=[4,1,8,4,5], listB=[5,6,1,8,4,5] 8 成功找到交点
listA=[1,9,1,2,4], listB=[3,2,4] 2 交点为 2
listA=[2,6,4], listB=[1,5] null 无交点

✅ 所有测试均通过,符合题目预期!


🌟 本期完结,下期见!🔥

👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!

💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪


📣 下一期预告:LeetCode 热题 100 第23题 ------ 17.反转链表(简单)

🔹 题目 :给你单链表的头节点 head,请将其反转,并返回反转后的链表。

🔹 核心思路:使用三指针法(前驱、当前、后继)逐步翻转指针方向。

🔹 考点:链表操作、指针重定向、递归 vs 迭代。

🔹 难度:简单,但却是链表操作的基础,常考于字节、腾讯、阿里等大厂笔试。

💡 提示:不要用栈或数组辅助!原地反转才是硬核!
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!


📌 特别提醒 :结合手写代码练习,形成肌肉记忆!

🚀 从今天开始,把每一道题变成你的"武器库"中的利器!

相关推荐
uhakadotcom33 分钟前
全面解析:GeoJSON 与 TopoJSON 的定义、差异及适用场景
前端·面试·github
兩尛38 分钟前
HJ98 喜欢切数组的红(dp
算法
adam_life40 分钟前
【P4551 最长异或路径】
算法·bfs·01字典树
前端缘梦41 分钟前
JavaScript核心机制:执行栈、作用域与this指向完全解析
前端·javascript·面试
CoovallyAIHub1 小时前
2025年值得关注的5款数据标注工具
深度学习·算法·计算机视觉
FuckPatience1 小时前
C# 补码
开发语言·算法·c#
稚辉君.MCA_P8_Java1 小时前
Gemini永久会员 VB返回最长有效子串长度
数据结构·后端·算法
小年糕是糕手1 小时前
【C++】类和对象(五) -- 类型转换、static成员
开发语言·c++·程序人生·考研·算法·visual studio·改行学it
Xの哲學1 小时前
Linux内核数据结构:设计哲学与实现机制
linux·服务器·算法·架构·边缘计算