Point Query 问题:给出个数,你需要(近似)回答的出现次数。

容易想到直接开一个桶,空间复杂度。我们希望在很大时用更小的空间复杂度解决这个问题。

Count-min Sketch

Count-min Sketch 是一个数据结构,通过随机哈希的方式来给出上述问题的近似解。

考虑构造一个随机哈希函数,将输入的元素均匀映射到个 bucket。

利用这个哈希来计算每个哈希值的出现次数:。那么记的真实出现次数为,显然有,在哈希不冲突时取等。

容易想到,多次采用不同的随机哈希分别统计出现次数,那么取可以很好地逼近

对此进行更细致的期望分析:

假设哈希函数是均匀随机,那么,于是

于是我们对该随机算法进行误差估计:

这里面用到了 Markov Inequality:若,有

可以控制上述概率不大于,那么我们取个独立随机哈希(若已知的上界),即可求出绝对误差在内的近似解。

空间复杂度

K-Heavy Hitter

K-Heavy Hitter 问题:给出个数,你需要(近似)求出出现次数前多的所有数。

对于上述问题我们仍然有空间复杂度的朴素算法。

近似 K-Heavy Hitter:要求必须求出所有出现次数不少于的数,并且求出的所有数的出现次数不少于

若已知,那么求近似解可以直接使用 Count-min Sketch 解决。考虑上界未知但上界已知的情况。

我们维护当前插入的总元素个数。在 Count-min Sketch 的基础上我们额外维护一个集合,用于记录 Count-min Sketch 意义下出现次数不小于的数。每次插入元素后,我们将出现次数小于的数从中删除。

可以发现近似 K-Heavy Hitter 的误差主要是留给 Count-min Sketch。而上述集合的维护其实是足够准确的。

由于出现次数不小于的数字个数是的,因此的空间大小有保证。在考虑到 Count-min Sketch 的误差后可以得出该算法的空间复杂度为

一个可能的代码实现:

class Solver {
public:
#define FOR(a, b, c) for(int a = (int)(b); a <= (int)(c); a++)
  int n1 = 0, k = 0; // current total numbers
  int* L, len = 0;

  static const int T = 6; // number of hash tables
  // const double eps = 0.01;
  static const int M = (5 * (1 << 20)) / T; // 2 / eps + 11; // size of hash table

  struct hash_table {
    int seed;
    int* bucket;

    hash_table() {}
    hash_table(int seed, int* bucket): seed(seed), bucket(bucket) {
      FOR(i, 0, M - 1) bucket[i] = 0;
    }
    void add(int val) {
      bucket[fasthash64(val, seed) % M]++;
    }
    int get(int val) {
      return bucket[fasthash64(val, seed) % M];
    }
  };
  hash_table h[T];

  int count_min(int e) {
    int res = h[0].get(e);
    FOR(i, 1, T - 1) res = min(res, h[i].get(e));
    return res;
  }
  // arr 是题目提供的内存空间,约为 32MB
  void init(int* arr, int m, int _k) {
    k = _k;
    FOR(i, 0, T - 1) {
      h[i] = hash_table(1145140 + i, arr + M * i);
    }
    L = arr + M * T;
  }
  void add(int e) {
    ++n1;
    FOR(i, 0, T - 1) h[i].add(e);

    FOR(i, 0, len - 1) {
      if (count_min(L[i]) < n1 * 1.0 / k) { // delete it
        L[i] = L[len - 1];
        --len, --i;
      }
    }

    if (count_min(e) >= n1 * 1.0 / k) {
      FOR(i, 0, len - 1) {
        if (L[i] == e) return;
      }
      L[len++] = e;
    }
  }
  std::vector<int> report() {
    return vector<int>(L, L + len);
  }
};