1. 题目解析与核心思路
这道PAT甲级真题"Sharing (25)"考察的是链表操作中的经典问题——寻找两个链表的第一个公共节点。题目通常会给出两个链表的头节点地址,要求找出它们合并的起始点。这类问题在实际开发中非常常见,比如在版本控制系统中查找分支合并点,或是在内存管理中检测内存块的共享情况。
1.1 问题本质理解
题目描述的"Sharing"实际上是指两个链表在某节点后完全重合,形成Y型结构。例如:
code复制List A: 1->2->3->4->5
List B: 9->8->4->5
这里的第一个共享节点是值为4的节点。
1.2 输入输出分析
典型输入格式为:
code复制第一行:两个链表的头节点地址 和 总节点数N
接下来N行:每个节点的地址、数据、下一个节点地址
输出要求给出第一个共享节点的地址,若无共享则输出-1。
2. 解题方法与算法选择
2.1 暴力解法及其局限
最直观的方法是遍历第一个链表的所有节点,对每个节点都在第二个链表中查找是否存在。这种方法时间复杂度为O(M*N),在PAT考试的数据规模下(通常N≤10^5)会超时。
注意:PAT考试对时间限制严格,一般要求时间复杂度在O(N)或O(NlogN)级别
2.2 哈希表法(O(N)空间)
使用哈希集合存储第一个链表的所有节点地址,然后遍历第二个链表查找第一个存在于集合中的节点。这种方法:
- 时间复杂度:O(M+N)
- 空间复杂度:O(M)或O(N)
虽然能通过,但不是最优解,且不符合题目"25分"分值对应的预期解法。
2.3 双指针法(最优解)
这是本题的标准解法,通过两个指针的巧妙移动实现O(1)空间复杂度:
- 指针pA从链表A头部开始,pB从链表B头部开始
- 同时向前移动,当到达末尾时切换到另一链表的头部
- 当pA == pB时即为第一个公共节点
原理在于:两指针走过的总长度都是A+B,最终会在交点相遇。
3. 具体实现与代码解析
3.1 数据结构定义
使用静态链表表示(PAT题目的常见形式):
c复制struct Node {
char data;
int next;
} nodes[100000];
3.2 双指针算法实现
c复制int findIntersection(int headA, int headB) {
int pA = headA, pB = headB;
while (pA != pB) {
pA = (pA == -1) ? headB : nodes[pA].next;
pB = (pB == -1) ? headA : nodes[pB].next;
}
return pA;
}
3.3 边界条件处理
需要特别注意:
- 有一个链表为空的情况
- 两个链表都为空的情况
- 没有交点的情况(循环结束返回-1)
4. 性能优化与技巧
4.1 提前终止判断
可以先遍历两个链表得到长度,如果尾节点不同则直接返回-1:
c复制int getLength(int head) {
int len = 0;
while (head != -1) {
len++;
head = nodes[head].next;
}
return len;
}
bool hasIntersection(int headA, int headB) {
while (nodes[headA].next != -1) headA = nodes[headA].next;
while (nodes[headB].next != -1) headB = nodes[headB].next;
return headA == headB;
}
4.2 内存访问优化
在PAT系统中,连续访问数组元素比随机访问更快。可以预先遍历链表将节点按顺序存储,减少缓存未命中。
5. 常见错误与调试技巧
5.1 典型错误案例
- 无限循环:忘记处理无交点情况,指针永远不相等
- 地址越界:没有验证节点地址是否有效(在PAT中通常保证有效)
- 静态数组大小不足:题目通常给出地址是5位数字,数组大小应至少100000
5.2 调试方法
- 打印指针移动路径:
c复制printf("pA: %05d, pB: %05d\n", pA, pB);
- 使用小规模测试用例手动验证
- 检查链表遍历终止条件是否为-1
6. 复杂度分析与扩展
6.1 时间复杂度
双指针法每个指针最多遍历A+B个节点,因此时间复杂度为O(M+N)。
6.2 空间复杂度
仅使用常数个额外变量,空间复杂度为O(1)。
6.3 问题变种
- 环形链表的交点检测(需结合快慢指针)
- 多个链表的公共交点
- 树结构的最近公共祖先(LCA)问题
7. 实际应用场景
这种算法在以下场景中有实际应用:
- 版本控制系统的分支合并检测
- 内存泄漏检测中的内存块追踪
- 社交网络中的共同好友查找
- 生物信息学中的序列比对
8. 完整参考代码
c复制#include <stdio.h>
struct Node {
char data;
int next;
} nodes[100000];
int main() {
int headA, headB, N;
scanf("%d %d %d", &headA, &headB, &N);
for (int i = 0; i < N; i++) {
int addr, next;
char data;
scanf("%d %c %d", &addr, &data, &next);
nodes[addr].data = data;
nodes[addr].next = next;
}
int pA = headA, pB = headB;
while (pA != pB) {
pA = (pA == -1) ? headB : nodes[pA].next;
pB = (pB == -1) ? headA : nodes[pB].next;
}
if (pA != -1) {
printf("%05d\n", pA);
} else {
printf("-1\n");
}
return 0;
}
9. 测试用例设计
9.1 常规测试用例
输入:
code复制11111 22222 7
11111 a 22222
22222 b 33333
33333 c 44444
44444 d 55555
55555 e -1
66666 f 44444
77777 g 66666
输出:44444
9.2 边界测试用例
- 无交点:
code复制11111 22222 3
11111 a -1
22222 b -1
33333 c -1
输出:-1
- 一个链表为空:
code复制-1 22222 1
22222 b -1
输出:-1
10. 学习路径建议
要掌握这类链表问题,建议:
- 先理解单链表的基本操作(插入、删除、遍历)
- 练习经典问题(反转、环检测、交点)
- 学习双指针技巧的各种应用
- 尝试用不同方法解决同一问题并比较优劣
在PAT备考中,类似的链表问题还包括:
- A1032 Sharing (本题)
- A1052 Linked List Sorting
- A1074 Reversing Linked List
- A1097 Deduplication on a Linked List
