【算法】【链表】160.相交链表--通俗讲解

90 阅读6分钟

算法通俗讲解推荐阅读
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解
【算法--链表】117.填充每个节点的下一个右侧节点指针Ⅱ--通俗讲解
【算法--链表】138.随机链表的复制--通俗讲解
【算法】143.重排链表--通俗讲解
【算法--链表】146.LRU缓存--通俗讲解
【算法--链表】147.对链表进行插入排序--通俗讲解
【算法】【链表】148.排序链表--通俗讲解


通俗易懂讲解“相交链表”算法题目

一、题目是啥?一句话说清

给定两个单链表,找出它们相交的起始节点;如果不存在相交节点,返回null。

示例:

  • 输入:链表A: 4→1→8→4→5,链表B: 5→6→1→8→4→5(相交于节点8)
  • 输出:节点8

二、解题核心

使用双指针法,两个指针分别从两个链表的头开始遍历,当指针到达链表末尾时,切换到另一个链表的头部继续遍历。如果链表相交,指针会在相交节点相遇;否则,会同时到达null。

这就像两个人分别从两个链表的起点开始走,如果走完自己的链表后走对方的链表,那么他们会在相交点相遇,因为两人走过的总长度相同。

三、关键在哪里?(3个核心点)

想理解并解决这道题,必须抓住以下三个关键点:

1. 指针的路径交换

  • 是什么:当指针遍历到链表末尾时,立即切换到另一个链表的头部继续遍历。
  • 为什么重要:这样确保了每个指针都遍历了两个链表的全部节点,总长度相同,从而在相交点相遇。

2. 相遇条件

  • 是什么:如果两个链表相交,指针会在相交节点相遇;如果不相交,指针会同时到达null。
  • 为什么重要:这是算法正确性的基础。相遇时返回节点,否则返回null。

3. 处理长度差异

  • 是什么:两个链表长度可能不同,但通过交换路径,指针走过的总长度相同(m+n)。
  • 为什么重要:无需计算链表长度,直接遍历即可处理长度差异,使算法简洁高效。

四、看图理解流程(通俗理解版本)

假设链表A: 4→1→8→4→5,链表B: 5→6→1→8→4→5,相交于节点8。

  1. 初始化:指针pA指向链表A的头(节点4),指针pB指向链表B的头(节点5)。
  2. 第一轮遍历
    • pA遍历A:4→1→8→4→5→null,然后切换到链表B的头(节点5)。
    • pB遍历B:5→6→1→8→4→5→null,然后切换到链表A的头(节点4)。
  3. 第二轮遍历
    • pA从链表B的头开始:5→6→1→8
    • pB从链表A的头开始:4→1→8
    • 当pA走到节点8时,pB也走到节点8,两者相遇,返回节点8。

如果不相交,例如链表A: 1→2→3,链表B: 4→5,则:

  • pA遍历:1→2→3→null→切换到B的头(4→5→null)
  • pB遍历:4→5→null→切换到A的头(1→2→3→null)
  • 两者同时到达null,返回null。

五、C++ 代码实现(附详细注释)

#include <iostream>
using namespace std;

// 链表节点定义
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == nullptr || headB == nullptr) return nullptr;
        
        ListNode *pA = headA;
        ListNode *pB = headB;
        
        // 当pA和pB不相同时继续循环
        while (pA != pB) {
            // 如果pA到达末尾,切换到headB;否则继续下一个
            pA = (pA == nullptr) ? headB : pA->next;
            // 如果pB到达末尾,切换到headA;否则继续下一个
            pB = (pB == nullptr) ? headA : pB->next;
        }
        
        // 返回pA(或pB),如果相交则是相交节点,如果不相交则是null
        return pA;
    }
};

// 测试代码
int main() {
    // 创建相交链表示例
    ListNode common(8);
    common.next = new ListNode(4);
    common.next->next = new ListNode(5);
    
    ListNode *headA = new ListNode(4);
    headA->next = new ListNode(1);
    headA->next->next = &common;
    
    ListNode *headB = new ListNode(5);
    headB->next = new ListNode(6);
    headB->next->next = new ListNode(1);
    headB->next->next->next = &common;
    
    Solution solution;
    ListNode *result = solution.getIntersectionNode(headA, headB);
    
    if (result != nullptr) {
        cout << "Intersected at " << result->val << endl;
    } else {
        cout << "No intersection" << endl;
    }
    
    // 释放内存(简单示例,实际中可能需要更完整的释放)
    delete common.next->next;
    delete common.next;
    delete headA->next;
    delete headA;
    delete headB->next->next;
    delete headB->next;
    delete headB;
    
    return 0;
}

六、时间空间复杂度

  • 时间复杂度:O(m+n),其中m和n分别是链表A和B的长度。每个指针最多遍历两个链表各一次。
  • 空间复杂度:O(1),只使用了两个指针,没有使用额外空间。

七、注意事项

  • 空链表处理:如果其中一个链表为空,直接返回null,因为不可能相交。
  • 循环终止条件:循环确保指针在相遇或同时为null时停止。注意,如果两个链表不相交,指针会同时为null,从而退出循环。
  • 节点比较:比较的是节点指针(地址)而不是值,因为值可能相同但节点不同。
  • 无环假设:题目假设链表无环,如果有环,则需要其他方法处理。

算法通俗讲解推荐阅读
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解
【算法--链表】117.填充每个节点的下一个右侧节点指针Ⅱ--通俗讲解
【算法--链表】138.随机链表的复制--通俗讲解
【算法】143.重排链表--通俗讲解
【算法--链表】146.LRU缓存--通俗讲解
【算法--链表】147.对链表进行插入排序--通俗讲解
【算法】【链表】148.排序链表--通俗讲解