2-5 链表去重 分数 5
作者 陈越
单位 浙江大学
给定一个带整数键值的链表 L,你需要把其中绝对值重复的键值结点删掉。即对每个键值 K,只有第一个绝对值等于 K 的结点被保留。同时,所有被删除的结点须被保存在另一个链表上。例如给定 L 为 21→-15→-15→-7→15,你需要输出去重后的链表 21→-15→-7,还有被删除的链表 -15→15。
输入格式: 输入在第一行给出 L 的第一个结点的地址和一个正整数 N(≤105,为结点总数)。一个结点的地址是非负的 5 位整数,空地址 NULL 用 -1 来表示。
随后 N 行,每行按以下格式描述一个结点:
其中地址是该结点的地址,键值是绝对值不超过104的整数,下一个结点是下个结点的地址。
输出格式: 首先输出去重后的链表,然后输出被删除的链表。每个结点占一行,按输入的格式输出。
输入样例: 1 2 3 4 5 6 00100 5 99999 -7 87654 23854 -15 00000 87654 15 -1 00000 -15 99999 00100 21 23854
输出样例: 1 2 3 4 5 00100 21 23854 23854 -15 99999 99999 -7 -1 00000 -15 87654 87654 15 -1
此题中的键值 ,指的是链表节点中存储的整数数据(即 data 字段的值)
c++答案(豆包) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 #include <iostream> #include <cstdio> #include <cmath> #include <unordered_set> using namespace std;struct Node { int addr; int data; int next; } nodes[100000 ]; int main () { int head, n; cin >> head >> n; for (int i = 0 ; i < n; ++i) { int addr, data, next; cin >> addr >> data >> next; nodes[addr] = {addr, data, next}; } unordered_set<int > exist; int res_head = -1 , res_tail = -1 ; int del_head = -1 , del_tail = -1 ; int current = head; while (current != -1 ) { int val = nodes[current].data; int abs_val = abs (val); if (exist.find (abs_val) == exist.end ()) { exist.insert (abs_val); if (res_head == -1 ) { res_head = current; res_tail = current; } else { nodes[res_tail].next = current; res_tail = current; } } else { if (del_head == -1 ) { del_head = current; del_tail = current; } else { nodes[del_tail].next = current; del_tail = current; } } current = nodes[current].next; } if (res_tail != -1 ) { nodes[res_tail].next = -1 ; } if (del_tail != -1 ) { nodes[del_tail].next = -1 ; } current = res_head; while (current != -1 ) { if (nodes[current].next == -1 ) { printf ("%05d %d -1\n" , nodes[current].addr, nodes[current].data); } else { printf ("%05d %d %05d\n" , nodes[current].addr, nodes[current].data, nodes[current].next); } current = nodes[current].next; } current = del_head; while (current != -1 ) { if (nodes[current].next == -1 ) { printf ("%05d %d -1\n" , nodes[current].addr, nodes[current].data); } else { printf ("%05d %d %05d\n" , nodes[current].addr, nodes[current].data, nodes[current].next); } current = nodes[current].next; } return 0 ; }
这题给我第一个反应:
咋给的链表各个元素不是按链表的连接顺序 (00100→23854→00000→99999→87654)排列的
而是模拟真实的内存存储场景 :链表的结点在内存中可能分散存储,仅通过指针(下一个地址)关联,而非连续排列。
因此,解题时需要先根据 “地址” 建立索引,再从起始地址开始,通过 “下一个结点地址” 依次遍历,才能还原出链表的实际顺序。
逻辑
数据存储 :使用数组nodes存储所有节点,下标为节点地址(利用题目中地址是 5 位整数的特性,直接映射)。
核心去重逻辑 :
用unordered_set记录已出现过的键值绝对值
遍历原链表,对每个节点判断其键值绝对值是否已存在
未存在的节点放入结果链表,已存在的放入删除链表
链表构建 :
分别维护两个链表(结果链表和删除链表)的头指针和尾指针
通过尾指针快速插入新节点,避免每次遍历查找尾节点
输出格式 :
地址需按 5 位整数格式输出(不足 5 位前补 0)
最后一个节点的下一个地址为-1
时间复杂度 :O (N),其中 N 为节点总数,只需遍历一次原链表即可完成去重。
unordered_set<int> exist; 是什么是 C++ 标准库中的哈希集合对象 ,核心作用是:
快速记录和判断 “整数是否已经出现过”,本质是一个不存储重复元素、查询效率极高的容器。
关键特性(对应本题用法):
去重性 :集合中不会存储重复元素(本题用于记录已出现的键值绝对值,避免重复保留节点)。
高效查询 :通过哈希表实现,判断元素是否存在的时间复杂度接近 O (1)(比数组遍历快得多,适合本题 1e5 规模的数据)。
核心操作 :
exist.insert(abs_val):将整数 abs_val 存入集合(标记为 “已出现”)。
exist.find(abs_val) == exist.end():判断 abs_val 是否不在集合中(若不在,说明是第一次出现,需保留对应节点)。
结构体数组 使用了结构体数组 来存储链表节点。
具体来说:
定义了结构体 struct Node,包含节点的地址(addr)、键值(data)和下一个节点的地址(next)。
声明了结构体数组 nodes[100000],数组的下标直接对应节点的地址 (因为题目中节点地址是 5 位整数,范围为 0~99999,刚好可以用数组下标覆盖)。
这种设计的优势是:
可以通过节点地址(如 addr)直接访问对应的节点数据,时间复杂度为 O (1)(例如 nodes[addr] 即可获取地址为 addr 的节点信息)。
避免了使用指针动态分配内存的复杂性,更适合处理题目中 “地址已知且范围固定” 的场景。
本质上,这是用数组模拟了链表的存储结构,通过 “地址 - 下标” 的映射实现快速访问,是处理这类链表问题 的高效方法。
c答案(豆包) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 #include <stdio.h> #include <stdlib.h> #include <math.h> typedef struct { int addr; int data; int next; } Node; #define HASH_SIZE 20001 int hash_table[HASH_SIZE] = {0 }; int main () { Node nodes[100000 ]; int head, n; scanf ("%d %d" , &head, &n); for (int i = 0 ; i < n; i++) { int addr, data, next; scanf ("%d %d %d" , &addr, &data, &next); nodes[addr].addr = addr; nodes[addr].data = data; nodes[addr].next = next; } int res_head = -1 , res_tail = -1 ; int del_head = -1 , del_tail = -1 ; int current = head; while (current != -1 ) { int val = nodes[current].data; int abs_val = abs (val); int hash_idx = abs_val % HASH_SIZE; if (hash_table[hash_idx] == 0 ) { hash_table[hash_idx] = 1 ; if (res_head == -1 ) { res_head = current; res_tail = current; } else { nodes[res_tail].next = current; res_tail = current; } } else { if (del_head == -1 ) { del_head = current; del_tail = current; } else { nodes[del_tail].next = current; del_tail = current; } } current = nodes[current].next; } if (res_tail != -1 ) { nodes[res_tail].next = -1 ; } if (del_tail != -1 ) { nodes[del_tail].next = -1 ; } current = res_head; while (current != -1 ) { if (nodes[current].next == -1 ) { printf ("%05d %d -1\n" , nodes[current].addr, nodes[current].data); } else { printf ("%05d %d %05d\n" , nodes[current].addr, nodes[current].data, nodes[current].next); } current = nodes[current].next; } current = del_head; while (current != -1 ) { if (nodes[current].next == -1 ) { printf ("%05d %d -1\n" , nodes[current].addr, nodes[current].data); } else { printf ("%05d %d %05d\n" , nodes[current].addr, nodes[current].data, nodes[current].next); } current = nodes[current].next; } return 0 ; }
相当于把c++中的unorderedset中的函数实现了一遍
用一个数组模拟 了 C++ 中 unordered_set 的核心功能,相当于手动实现了 “记录元素是否存在” 的逻辑。
具体来说:
C++ 中 unordered_set<int> exist 的核心作用是快速判断一个整数(绝对值)是否已经出现过 (通过 find 方法),以及记录新出现的整数 (通过 insert 方法)。
C 语言中没有内置的哈希集合,因此我们用hash_table数组模拟了这个过程:
用数组下标对应 “键值的绝对值”(通过哈希映射),数组元素值为 0 表示 “未出现”,1 表示 “已出现”。
判断是否存在:直接检查 hash_table[hash_idx] 是否为 1(对应 C++ 的 exist.find(...) != exist.end())。
记录新元素:将 hash_table[hash_idx] 设为 1(对应 C++ 的 exist.insert(...))。
这种实现本质上是用数组模拟了一个简单的哈希集合 ,实现了 unordered_set 在本题中所需的核心功能(存在性判断和插入),只是实现方式更底层(依赖数组下标和哈希映射)。
由于本题中键值的绝对值范围有限(≤10⁴),用数组模拟哈希表不仅简单,而且效率很高(无哈希冲突),完全能满足需求