【C++】AcWing在线题库 1587. 链表重复数据删除

原题连接

  • 问题描述

给定一个单链表 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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值