1. 题目描述
现有 n 本魔法相册,每本相册中有若干张照片,每张照片由唯一的标识符 id 和时间戳 t 组成。
在一个相册内部,所有照片的 id 互不相同,同一 id 的照片在不同相册中出现,代表这是同一张照片,并且对应的 t 相同。
现在,需要找出所有在 超过一本 相册中出现过的照片,并统计它们在所有相册中出现的 总次数。将这些重复的照片按照时间戳 t 从小到大排序后输出。
-
输入:
- 第一行:整数
n,代表有n本相册。 - 接下来的
n行:每行代表一本相册,每行有若干个由空格分隔的整数,每两个为一对,前一个为照片的id,后一个为时间戳t。
- 第一行:整数
-
输出:
- 统计在超过一本相册中出现过的照片。
- 按时间戳
t从小到大顺序输出。 - 输出格式:
id1 count1 id2 count2 ...,其中id表示照片的id,count表示该照片出现的次数。
输入和输出的具体形式可以参考下面两张图:
2. 题目与难点分析
首先输入具有不确定性,读取输入的第一行 n 可以知道相册的数量,但是无法知道每本相册中的照片数量。
其次,需要同时记录三个数据,照片 id,时间戳 t,出现次数 count。
最后,还需要想想怎么通过时间戳 t 进行排序,从而让 t 对应照片的 id 和 count 按照 t 从小到大打印出来。
3. 解题思路
题目要求根据 id 统计照片出现次数并保留时间戳,并明确强调了 id 的唯一性,这种 基于唯一主键对某个属性进行累加 的特征正是典型的哈希表场景。
下面就要分两种情况了:
- 如果
id不在哈希表中,我们需要将该id作为键,并录入它对应的时间戳t,再把出现次数count置位1。 - 如果
id在哈希表中,我们只用将该id对应的count加一即可。
那么问题又来了,我们要存的值有两个,分别是时间戳 t 和出现次数 count ,哈希表该怎么定义呢?
一种办法是定义一个结构体,把 t 和 count 都放进去。还有一种方法是用 pair<> 专门存放一对数据。
因此,我们的哈希表应该这样定义:
//<id, pair<t, count>>
unordered_map<int, pair<int, int>> photomap;
第二章提到了一个难点是我们不知道每本相册的照片数量,现在要想办法解决这个问题。
这里其实有一个处理技巧:
- 我们可以先用
getline读取一整行的数据,也就是先拿到一整本相册。 - 然后使用
stringstream将字符串转回流,每次往出拿一对id和t,直到拿完。
除此之外,还需要想想怎么进行排序,毕竟哈希表是无序的,一种策略是将哈希表的内容先存到动态数组里面,然后使用 sort 进行排序,但是考虑到,最后需要打印出 id 和 count ,并且排序时需要用到 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 已经存在,所以只会单纯的将 count 加 1。
sort 中采用了 Lambda 表达式排序:[](const Result& a, const Result& b) { return a.t < b.t; },这种写法直接在 sort 函数内定义排序规则,不需要额外写比较函数。
程序最开头,cin >> n 读取了数字,但换行符 \n 还留在缓冲区参考第一章的输入示意图。如果直接调用 getline,它会读到一个空行,因此必须先用一个 dummy 字符串将换行符拿走。
最后贴一下代码检测的结果:
本文结束。