这道PAT考试真题考察的是静态链表的去重操作,属于数据结构与算法中的经典题型。静态链表是一种用数组模拟链表的数据结构,在内存受限或需要快速随机访问的场景下非常实用。题目要求我们处理一个包含整数键值的静态链表,将所有键值的绝对值重复的节点移除,并按照原顺序输出处理后的链表和被移除的节点链表。
在实际工程中,类似的操作常见于内存管理、文件系统索引等场景。比如操作系统需要维护一个内存块链表,当检测到重复内存块时需要将其移出主链表。因此掌握这种基础算法对理解底层系统原理很有帮助。
静态链表使用数组存储节点,每个节点包含两个字段:
与动态链表相比,静态链表的优势在于:
本题采用哈希集合辅助判重的方案,具体流程如下:
这种算法的时间复杂度是O(n),空间复杂度是O(n),属于最优解。哈希集合的查找操作平均时间复杂度为O(1),保证了整体效率。
cpp复制const int N=1e6+10;
struct SList{
int k, next=-1; // k为键值,next为下一个节点地址
}l1[N], l2[N]; // l1是主链表,l2是移除的节点链表
unordered_set<int> s; // 哈希集合用于判重
这里定义了:
cpp复制int start, n;
cin >> start >> n;
for(int i=1; i<=n; ++i){
int curr, k, next;
cin >> curr >> k >> next;
l1[curr].next = next;
l1[curr].k = k;
}
输入处理时需要注意:
cpp复制int curr = start;
int start2 = -1, curr2 = -1; // 初始化移除链表的头和当前节点
s.insert(abs(l1[start].k)); // 将头节点键值加入集合
while(curr != -1 && l1[curr].next != -1){
int nextcurr = l1[curr].next;
if(!s.count(abs(l1[nextcurr].k))){
s.insert(abs(l1[nextcurr].k));
curr = nextcurr;
continue;
}
// 处理重复节点
if(start2 == -1){
start2 = nextcurr;
curr2 = start2;
l2[curr2].next = -1;
l2[curr2].k = l1[nextcurr].k;
}
else{
l2[curr2].next = nextcurr;
curr2 = nextcurr;
l2[curr2].k = l1[nextcurr].k;
l2[curr2].next = -1;
}
l1[curr].next = l1[nextcurr].next; // 从主链表中移除
}
这段代码实现了核心的去重逻辑:
cpp复制curr = start;
while(curr != -1){
cout << setfill('0') << setw(5) << curr << ' '
<< l1[curr].k << ' ';
if(l1[curr].next != -1){
cout << setfill('0') << setw(5) << l1[curr].next << '\n';
}
else cout << "-1\n";
curr = l1[curr].next;
}
curr = start2;
while(curr != -1){
cout << setfill('0') << setw(5) << curr << ' '
<< l2[curr].k << ' ';
if(l2[curr].next != -1){
cout << setfill('0') << setw(5) << l2[curr].next << '\n';
}
else cout << "-1\n";
curr = l2[curr].next;
}
输出时需要注意:
常见错误包括:
解决方案:
当n很大时(如1e6级别):
应包含以下情况:
在实际刷题过程中,我发现静态链表题目虽然基础,但非常考验对指针操作和边界条件的把握。建议初学者从小的测试用例开始,逐步验证每个步骤的正确性。另外,输出格式的细节往往决定成败,需要特别关注题目中的格式要求。