问题描述
给你一个整数数组 nums 和一个整数 k,请你用一个字符串返回其中出现频率前 k 高的元素。请按升序排列。
你所设计算法的时间复杂度必须优于 O(n log n),其中 n 是数组大小。
参数限制
这道题如果看对眼了那就是纯纯的简单题 可是我不小心把nums的范围看成了1e14还有救吗>_< 当然有,而且包有的
我是C++选手,因此以下的题解会从C++编写的角度去考虑
时间复杂度并不是这道题的难点。快速排序,堆排,归并,有很多种方式实现n个元素的nlogn排序,这里我们不提这些排序的实现方式,况且C++自带的sort足够好用。
观察参数限制,我们发现这道题的难点在于统计每个数出现的次数。 开这么大的数组显然是不可能的,于是我们有两种实现方式。
第一种是直接用STL里面自带的map。map在STL中的实现方式是红黑树,那么时间复杂度一次查询也是logn,所以直接可行
还有一种是手动编写哈希表。 哈希表的基础思想是将大数对一个较小的素数取模,作为大数的数组下标,以此来达到存储的效果。 但是对于两个数,二者的模可能是相同的,那么这个时候我们加上链表。 将同一模数的大数构建链表,均摊下来链表的长度很小可以忽略。
若是这种方案不能让人满意,我们依旧有多重哈希的方案。 选用多个模数,大数对分别对模数取余后形成的结果我们将其构成一个pair,把pair作为下标,以此来达到区分的效果。
事实上单哈希被数据卡掉的概率已经很小,多重哈希更是不可能,除非出题人对特定的模数进行了预判并且成功构造数据。我遇到过。所以还是选用链表哈希。
本质上哈希仍然是一种随机化算法,但实际上它的准确率可以做到非常高,我们常常忽略掉那些误差。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <class T>
inline void read(T &x)
{
int fg = 1; char ch = getchar(); x = 0;
for(;ch < '0' || ch > '9';ch = getchar()) fg = ch == '-' ? -1 : 1;
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + (ch ^ '0'); x *= fg;
}
const int P = 70921;
const int N = 1e5 + 5;
int f(int x) { return (x % P + P) % P; }
struct hash_map
{
int last[N],cnt = 0;
struct node{ int nxt,key,tot; } a[N];
void insert(int x)
{
int fg = 1;
for(int p = last[f(x)];p;p = a[p].nxt)
if(a[p].key == x) { ++a[p].tot, fg = 0; break; }
if(fg) a[++cnt] = (node){ last[f(x)],x,1 }, last[f(x)] = cnt;
}
} hash;
int n,k,a[N],tot = 0,stack[N],tot_e;
bool map[N];
struct element{ int key,cnt; } e[N];
bool operator < (element a,element b) { return a.cnt > b.cnt; }
int main()
{
freopen("P27.in","r",stdin);
freopen("P27.out","w",stdout);
read(n), read(k);
for(int i = 1;i <= n; ++ i)
{
read(a[i]), hash.insert(a[i]);
if(!map[f(a[i])]) map[f(a[i])] = 1, stack[++tot] = f(a[i]);
}
for(int i = 1;i <= tot; ++ i)
for(int p = hash.last[stack[i]];p;p = hash.a[p].nxt)
e[++tot_e] = (element){ hash.a[p].key,hash.a[p].tot };
sort(e + 1,e + 1 + tot_e);
for(int i = 1;i <= k; ++ i)
printf("%d ",e[i].key);
fclose(stdin); fclose(stdout);
return 0;
}