To solve approximate top-k in tight memory, I'll present the Space-Saving algorithm (best for exact top-k heavy hitters with small constant error) and contrast Count-Min Sketch (CMS) when we want point queries and mergeability. I'll give Java-like pseudocode for Space-Saving, explain error bounds, memory layout, and show how to merge shards for both approaches.Approach summary:- Space-Saving: maintain m counters (m determined by memory, e.g., 10MB -> number of counters). For each incoming item: - If item present, increment its counter. - Else if free slot exists, insert with count=1. - Else replace the minimum counter: set key=item, count=minCount+1, and remember error=previous minCount.- Guarantees: reported count >= true count - error. For any item not in table, estimated count ≤ true count ≤ estimated + total_stream_count/m.- CMS alternative: uses w×d table hashed; gives estimated count ≥ true count and an additive error <= εN with probability 1-δ. CMS is mergeable by summing tables.Java-like Space-Saving (memory-compact, O(1) update amortized):java
// Each bucket: key (long/int/hash), count (long), err (long)
class Bucket { long key; long count; long err; boolean used; }
class SpaceSaving {
Bucket[] buckets; // size m
int m;
SpaceSaving(int m){ this.m=m; buckets = new Bucket[m]; for(int i=0;i<m;i++) buckets[i]=new Bucket(); }
void update(long key){
int idx = findIndex(key);
if(idx!=-1){ buckets[idx].count++; return; }
int free = findFree();
if(free!=-1){ buckets[free].key=key; buckets[free].count=1; buckets[free].err=0; buckets[free].used=true; return; }
int minIdx = findMinIndex();
long minCount = buckets[minIdx].count;
buckets[minIdx].key = key;
buckets[minIdx].err = minCount;
buckets[minIdx].count = minCount + 1;
}
// linear scan helpers; in practice use hashmap + min-heap or doubly-linked freq lists for O(1)
int findIndex(long key){ for(int i=0;i<m;i++) if(buckets[i].used && buckets[i].key==key) return i; return -1; }
int findFree(){ for(int i=0;i<m;i++) if(!buckets[i].used) return i; return -1; }
int findMinIndex(){ int mi=0; for(int i=1;i<m;i++) if(buckets[i].count < buckets[mi].count) mi=i; return mi; }
List<Bucket> topK(int k){
List<Bucket> list = Arrays.asList(buckets);
list = list.stream().filter(b->b.used).sorted((a,b)->Long.compare(b.count,a.count)).limit(k).collect(Collectors.toList());
return list;
}
}
Key points and optimizations:- Memory layout: store arrays of (key, count, err). If key hashed to 64-bit, count/err 64-bit. With 10MB = ~10*2^20 bytes ≈ 10,485,760 bytes; per-counter 24 bytes ⇒ ~436k counters. Use compact integers if domain permits.- Performance: naive findIndex is O(m), real implementation uses hashmap from key->bucket and a pointer to min (min-heap or doubly-linked freq list) for O(1) updates and O(log m) replacement.- Error bounds: for Space-Saving, estimated_count ≥ true_count - N/m, where N is total items seen. Rank guarantee: items with true frequency > N/m must appear in table.- Merging shards: - Space-Saving: to merge two summaries A and B, for each bucket in B do update(A, key, count_estimated) by adding count_estimated and err appropriately — a safe merge is to treat each bucket as a weighted stream: call update key count times or better: implement a merge routine that for every (k, c, e) in B, adds c to A's counter for k (if exists) else inserts with count=c and err=e, and then run shrink step to keep only m counters. This preserves the same guarantees with combined N. - CMS: merge by elementwise addition of matrices. CMS provides additive error εN with probability 1-δ; choose w=ceil(e/ε), d=ceil(ln(1/δ)). CMS easier to merge across shards.Edge cases:- Extremely large key domain → use hashing for keys in buckets or use CMS.- Keys with very low frequency — false positives/negatives possible within error bound.- Choosing m: trade-off between memory and error; for Space-Saving error ~N/m.When asked in interview, explain trade-offs: Space-Saving gives tighter top-k guarantees and exact identities; CMS is simpler, mergeable, and good for frequency queries but needs heavy hitters extraction via additional structures (e.g., heavy-hitter heap seeded from CMS).