-
问题描述
给定一个单链表 L,链表上的每个节点都存有一个键值,你应该删掉其中拥有重复键值绝对值的节点。
也就是说,对于每个值 K,只保留键值或键值绝对值为 K 的第一个节点。
同时,被删掉的节点也应保存在一个单独的链表中。
例如,给定 L 为 21→-15→-15→-7→15
,则删除重复数据后的链表为 21→-15→-7
,已删除链表为 -15→15
。
输入格式
第一行首先包含头节点地址,然后包含节点总数 N。
节点地址用一个 5 位非负整数表示(可能有前导 0),NULL 用 −1 表示。
接下来 N 行,每行描述一个节点的信息,格式如下:
Address Key Next
其中 Address
是节点地址,Key
是一个整数表示键值,Next
是下一个节点的地址。
输出格式
首先按顺序输出结果链表,然后按顺序输出删除链表。
每个节点占一行,格式与输入相同。
数据范围
1≤N≤10^5,
节点键值的绝对值不会超过 10^4。
输入样例:
00100 5
99999 -7 87654
23854 -15 00000
87654 15 -1
00000 -15 99999
00100 21 23854
输出样例:
00100 21 23854
23854 -15 99999
99999 -7 -1
00000 -15 87654
87654 15 -1
-
算法分析
对于本题,要做的事情有:1.从输入构建出对应的链表;2.删除“重复”值的结点,并构造删除链表;3.遍历原链表和删除链表,打印输出。
结点定义如下:
struct ListNode
{
int val;
int address; // 对应的“地址”
ListNode *next;
ListNode(int x, int address) : val(x), address(address), next(NULL) {}
};
(1)从输入构建出对应的链表
由于N行输入中每行只给出每个结点的地址、值和下地址,在获取完整输入之前,我们不可能知道所有结点之间的连接情况。又由于题目中说到地址范围是0-999999,因此这里我定义了一个大小为10^5的结点指针数组nodes,每个元素初始化为空指针:
ListNode *nodes[100000] = {0}; // 结点指针数组,codes[x]表示地址为x的结点
除此之外,还要设置一个数组bucket:
int bucket[10000] = {0}; // bucket[x]表示绝对值为x的结点的个数
每次读取一行输入时,就设置相应结点指针,相应bucket元素加1:
int address, key, next;
for (int i = 0; i < N; i++)
{
cin >> address >> key >> next;
bucket[abs(key)]++;
if (nodes[address])
{
nodes[address]->val = key;
}
else
{
nodes[address] = new ListNode(key, address);
}
if (next != -1)
{
if (!nodes[next])
{
nodes[next] = new ListNode(0, next);
}
nodes[address]->next = nodes[next];
}
}
(2)删除“重复”结点,并构建删除链表
先定义一个向量v,元素类型为结点指针,遍历一遍链表,把所有结点都依次存进v里。然后反向遍历v,每次访问一个结点,先判断其val对应的bucket值是否大于1,如果是的话,由于我们是反向遍历,“重复”结点都在后面,因此说明该结点是“重复”结点,我们要将其从原结点删除,并添加到删除链表中。可以采用头插法来添加到删除链表,在下面头插法注释处有写到:
/*删除重复元素,构建删除链表*/
vector<ListNode *> v;
v.reserve(10000);
ListNode *p = head,
*delHead = 0; // 删除链表头结点
while (p)
{
v.push_back(p);
p = p->next;
}
for (int i = v.size() - 1; i >= 1; i--)
{
if (bucket[abs(v[i]->val)] > 1)
{
bucket[abs(v[i]->val)]--;
v[i - 1]->next = v[i]->next;
/*头插法构建删除链表*/
ListNode *temp = delHead;
delHead = v[i];
delHead->next = temp;
}
}
/*head单独处理*/
if (bucket[head->val] > 1)
{
bucket[abs(head->val)]--;
head = head->next;
/*头插法构建删除链表*/
ListNode *temp = delHead;
delHead = p;
delHead->next = temp;
}
最后,head就是去掉“重复”结点后的链表的头结点,delHead就是删除链表头结点。
(3)遍历原链表和删除链表,打印输出
分别从head和delHead开始遍历打印就可以了,注意前导零的输出。(一直觉得printf更好用)
-
完整代码
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
struct ListNode
{
int val;
int address; // 对应的“地址”
ListNode *next;
ListNode(int x, int address) : val(x), address(address), next(NULL) {}
};
ListNode *nodes[100000] = {0}; // 结点指针数组
int bucket[10000] = {0};
int main()
{
/*构建链表*/
int headAddress, N;
cin >> headAddress >> N;
/*头结点*/
nodes[headAddress] = new ListNode(0, headAddress);
ListNode *head = nodes[headAddress];
int address, key, next;
for (int i = 0; i < N; i++)
{
cin >> address >> key >> next;
bucket[abs(key)]++;
if (nodes[address])
{
nodes[address]->val = key;
}
else
{
nodes[address] = new ListNode(key, address);
}
if (next != -1)
{
if (!nodes[next])
{
nodes[next] = new ListNode(0, next);
}
nodes[address]->next = nodes[next];
}
}
/*删除重复元素,构建删除链表*/
vector<ListNode *> v;
v.reserve(10000);
ListNode *p = head,
*delHead = 0; // 删除链表头结点
while (p)
{
v.push_back(p);
p = p->next;
}
for (int i = v.size() - 1; i >= 1; i--)
{
if (bucket[abs(v[i]->val)] > 1)
{
bucket[abs(v[i]->val)]--;
v[i - 1]->next = v[i]->next;
/*头插法构建删除链表*/
ListNode *temp = delHead;
delHead = v[i];
delHead->next = temp;
}
}
/*head单独处理*/
if (bucket[head->val] > 1)
{
bucket[abs(head->val)]--;
head = head->next;
/*头插法构建删除链表*/
ListNode *temp = delHead;
delHead = p;
delHead->next = temp;
}
/*打印输出*/
p = head;
while (p)
{
printf("%05d %d", p->address, p->val);
if (p->next)
{
printf(" %05d\n", p->next->address);
}
else
{
printf(" -1\n");
}
p = p->next;
}
p = delHead;
while (p)
{
printf("%05d %d", p->address, p->val);
if (p->next)
{
printf(" %05d\n", p->next->address);
}
else
{
printf(" -1\n");
}
p = p->next;
}
return 0;
}