【华为机考真题】魔法相册的重复记忆 C++ 实现

0 阅读5分钟

1. 题目描述

现有 n 本魔法相册,每本相册中有若干张照片,每张照片由唯一的标识符 id 和时间戳 t 组成。

在一个相册内部,所有照片的 id 互不相同,同一 id 的照片在不同相册中出现,代表这是同一张照片,并且对应的 t 相同。

现在,需要找出所有在 超过一本 相册中出现过的照片,并统计它们在所有相册中出现的 总次数。将这些重复的照片按照时间戳 t 从小到大排序后输出。

  • 输入:

    • 第一行:整数 n ,代表有 n 本相册。
    • 接下来的 n 行:每行代表一本相册,每行有若干个由空格分隔的整数,每两个为一对,前一个为照片的 id ,后一个为时间戳 t
  • 输出:

    • 统计在超过一本相册中出现过的照片。
    • 按时间戳 t 从小到大顺序输出。
    • 输出格式:id1 count1 id2 count2 ...,其中 id 表示照片的 idcount 表示该照片出现的次数。

输入和输出的具体形式可以参考下面两张图:

1. 输入描述.png

2. 输出描述.png

2. 题目与难点分析

首先输入具有不确定性,读取输入的第一行 n 可以知道相册的数量,但是无法知道每本相册中的照片数量。

其次,需要同时记录三个数据,照片 id,时间戳 t,出现次数 count

最后,还需要想想怎么通过时间戳 t 进行排序,从而让 t 对应照片的 idcount 按照 t 从小到大打印出来。

3. 解题思路

题目要求根据 id 统计照片出现次数并保留时间戳,并明确强调了 id 的唯一性,这种 基于唯一主键对某个属性进行累加 的特征正是典型的哈希表场景。

下面就要分两种情况了:

  • 如果 id 不在哈希表中,我们需要将该 id 作为键,并录入它对应的时间戳 t,再把出现次数 count 置位1。
  • 如果 id 在哈希表中,我们只用将该 id 对应的 count 加一即可。

那么问题又来了,我们要存的值有两个,分别是时间戳 t 和出现次数 count ,哈希表该怎么定义呢?

一种办法是定义一个结构体,把 tcount 都放进去。还有一种方法是用 pair<> 专门存放一对数据。

因此,我们的哈希表应该这样定义:

//<id, pair<t, count>>
unordered_map<int, pair<int, int>> photomap;

第二章提到了一个难点是我们不知道每本相册的照片数量,现在要想办法解决这个问题。

这里其实有一个处理技巧:

  • 我们可以先用 getline 读取一整行的数据,也就是先拿到一整本相册。
  • 然后使用 stringstream 将字符串转回流,每次往出拿一对 idt,直到拿完。

除此之外,还需要想想怎么进行排序,毕竟哈希表是无序的,一种策略是将哈希表的内容先存到动态数组里面,然后使用 sort 进行排序,但是考虑到,最后需要打印出 idcount ,并且排序时需要用到 t,因此我们可以定义一个结构体把这三个数据都存进去,如下:

struct my_data{
    int id;
    int t;
    int count;
};

4. 完整代码

完整的实现代码如下:

#include <iostream>
#include <vector>
#include <unordered_map>
#include <string>
#include <sstream>
#include <algorithm>using namespace std;
​
struct my_data{
    int id;
    int t;
    int count;
};
​
int main() {
   int n;
   cin >> n;
​
    //读取n后面的换行符
   string dummy;
   getline(cin, dummy);
​
   unordered_map<int, pair<int, int>> photomap;
​
    //存入哈希表
   for(int i=0; i<n; i++)
   {
        string line;
        if(!getline(cin, line)) break;
​
        stringstream ss(line);//将字符串转成流
        int id,t;
        while(ss >> id >> t)//每次拿出来一对id和t
        {
            if(photomap.count(id) == 0)//如果哈希表中没有该id
            {
                photomap[id].first = t;//存入时间戳
            }
            photomap[id].second++;//count+1
        }
   }
​
    //数据搬运到动态数组
    vector<my_data> res;
​
    for(auto const &i : photomap)
    {
        if(i.second.second > 1)//当照片出现次数大于1,才需要搬运
        {
            my_data temp;
            temp.id = i.first;
            temp.t = i.second.first;
            temp.count = i.second.second;
            res.push_back(temp);
        }
    }
​
    //排序
    sort(res.begin(),res.end(), [](const my_data& a, const my_data& b){
        return a.t < b.t;
    });
​
    for(int i=0; i<res.size(); i++)
    {
        cout << res[i].id << " " << res[i].count;
        if(i!= res.size() - 1)//注意输出格式
        {
            cout << " ";
        }
    }
    cout << endl;
​
    return 0;
}

5. 部分代码讲解

stringstream 是处理 ACM 模式下 一行多个输入 的工具,它 将字符串模拟成输入流,自动帮我们处理空格和类型转换。

哈希表的查找和插入复杂度接近 O(1),在照片数量巨大时,性能远超线性查找。

对于 C++ 中的哈希表,当访问一个不存在的 key 时,会自动创建一个默认纸,对于 int 来说,该值为 0 ,这也是为什么我采用了下面的处理方法:

if(photomap.count(id) == 0)//如果哈希表中没有该id
{
    photomap[id].first = t;//存入时间戳
}
photomap[id].second++;//count+1

当发现哈西表中没有该 id 时,执行 photomap[id].first = t,这时程序会发现哈希表中并不存在这个 id,于是立刻在哈希表里为这个 id 创建一块空间。我们定义的哈希表的值类型为 pair<int, int>,对于 int 类型,值初始化就是 0,所以此时,这个 id 对应的 pair 变成了 {0, 0},再然后执行赋值操作,pair 变成了 {t, 0},在 if 之后,pair 又变成了{t, 1}。这样就完成了对于一个新的id,将它的时间戳初始化为 t,出现次数初始化为 count 的操作。

在后面的每一轮循环中,如果再次发现该 id,由于该 id 已经存在,所以只会单纯的将 count1

sort 中采用了 Lambda 表达式排序[](const Result& a, const Result& b) { return a.t < b.t; },这种写法直接在 sort 函数内定义排序规则,不需要额外写比较函数。

程序最开头,cin >> n 读取了数字,但换行符 \n 还留在缓冲区参考第一章的输入示意图。如果直接调用 getline,它会读到一个空行,因此必须先用一个 dummy 字符串将换行符拿走。

最后贴一下代码检测的结果:

3. 运行结果.png


本文结束。