算法通俗讲解推荐阅读
【算法--链表】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。
- 初始化:指针pA指向链表A的头(节点4),指针pB指向链表B的头(节点5)。
- 第一轮遍历:
- pA遍历A:4→1→8→4→5→null,然后切换到链表B的头(节点5)。
- pB遍历B:5→6→1→8→4→5→null,然后切换到链表A的头(节点4)。
- 第二轮遍历:
- 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.排序链表--通俗讲解