Pairwise Annihilation & The $k$-Tuple Cancellation Invariant
Suppose a stream of length $N$ contains a strict majority element $m$ with frequency $f(m) > N / 2$. Imagine pairing each occurrence of $m$ with one distinct element $x \neq m$ and deleting both from the stream. Because all non-$m$ elements combined total $N - f(m) < N / 2$, they cannot annihilate all occurrences of $m$! Robert S. Boyer and J Strother Moore (1981) turned this observation into an $O(N)$ time, $O(1)$ space streaming algorithm with two variables (candidate, count):
- If
count == 0, adopt the current element $x$ ascandidate = xand setcount = 1. - Else if
x == candidate, increment++count. - Else (
x != candidate), annihilate the pair by decrementing--count.
In 1982, Jayadev Misra and David Gries generalized this to find all elements occurring strictly more than $\lfloor N / k \rfloor$ times using at most $k - 1$ candidate counters: when a new element $x$ arrives and is not among the $k - 1$ tracked keys (and all $k - 1$ slots are non-zero), decrement all $k - 1$ counters by 1—forming a cancelled $k$-tuple of $k$ mutually distinct elements!
Let $f(x)$ be the true frequency of element $x$ in a stream of length $N$, and let $\hat{f}(x)$ be its surviving counter in a Misra-Gries summary of capacity $k - 1$ ($\hat{f}(x) = 0$ if $x$ is not tracked). Every decrement step removes $1$ count from $k$ distinct elements simultaneously (the incoming element $x$ plus the $k - 1$ active candidates). Let $D$ be the total number of $k$-tuple decrement events across the entire stream. Since each event consumes $k$ elements from the total stream of size $N$:
$$k \cdot D \le N - \sum_{y} \hat{f}(y) \le N \implies D \le \frac{N}{k}$$
Because element $x$ can lose at most $1$ count per decrement event $D$, its surviving counter satisfies:
$$f(x) - \frac{N}{k} \le f(x) - D \le \hat{f}(x) \le f(x)$$
Therefore, every element with $f(x) > N / k$ is guaranteed to have $\hat{f}(x) > 0$ at the end of Pass 1! Furthermore, Misra-Gries summaries are mergeable across distributed shards by adding counter maps and repeatedly subtracting the $k$-th largest count.
Streaming Frequency Estimation Algorithms Compared
| Algorithm | Target Threshold | Space Complexity | Update Time | Deterministic Guarantee? |
|---|---|---|---|---|
| Boyer-Moore ($k = 2$) | $f(x) > N / 2$ | $O(1)$ (2 scalars) | $O(1)$ | Deterministic (Pass 2 verifies if majority isn't promised) |
| Misra-Gries ($k = 3$, Majority II) | $f(x) > N / 3$ | $O(1)$ (4 scalars) | $O(1)$ | Deterministic (at most 2 elements can exceed $N/3$) |
| General Misra-Gries / Frequent | $f(x) > N / k$ (or error $\varepsilon N$) | $O(k)$ ($k - 1$ counters) | $O(1)$ amortized | Deterministic: $f(x) - N/k \le \hat{f}(x) \le f(x)$ |
| Count-Min Sketch | Point query $\pm \varepsilon N$ w.p. $1 - \delta$ | $O(\frac{1}{\varepsilon} \log \frac{1}{\delta})$ | $O(\log \frac{1}{\delta})$ | Probabilistic upper bound ($\hat{f}(x) \ge f(x)$); supports deletions |
Visualizing $k$-Tuple Cancellation ($k = 3$, Finding Elements $> N / 3$)
Two-Pass Verification Pipeline for Heavy Hitters
through k-1 Misra-Gries slots"] --> B["Produces <= k-1
surviving candidates"] B --> C["Reset counters to 0
(Keep candidate keys)"] C --> D["Pass 2: Count exact frequencies
of surviving candidates"] D --> E["Filter candidates with
exact_count > N / k"]
For $k = 2$ (Boyer-Moore) and $k = 3$ (2 candidates), the entire state fits inside 2 to 4 general-purpose CPU registers (rax, rbx, rcx, rdx) with zero memory allocations and zero L1 cache traffic during the scan. Even more importantly for distributed telemetry engines (such as Google Monarch or ClickHouse), Boyer-Moore and Misra-Gries summaries form a commutative monoid approximation: two worker nodes can scan 1 billion log lines each in parallel and merge their $(cand, count)$ pairs in $O(k)$ time by adding matching candidate counts and subtracting the $k$-th largest count from all survivors.
Watch distinct stream elements mutually annihilate each other: any element occurring > N/2 times (or > N/k in Misra-Gries) is mathematically guaranteed to survive.
Invariant & State
Production Implementation: Boyer-Moore & $k=3$ Misra-Gries with 2-Pass Verification
#include <cstdint>
#include <optional>
#include <vector>
// Boyer-Moore Majority Vote with mandatory verification pass
std::optional<int> find_majority_element(const std::vector<int>& nums) noexcept {
int candidate = 0;
std::size_t count = 0;
for (int x : nums) {
if (count == 0) {
candidate = x;
count = 1;
} else if (x == candidate) {
++count;
} else {
--count;
}
}
// Pass 2: Verify that candidate actually occurs > N / 2 times
std::size_t exact = 0;
for (int x : nums) {
if (x == candidate) ++exact;
}
if (exact > nums.size() / 2) return candidate;
return std::nullopt;
}
// Misra-Gries (k = 3): Find all elements appearing strictly more than floor(N / 3) times
std::vector<int> majority_element_n_over_3(const std::vector<int>& nums) {
int cand1 = 0, cand2 = 1; // Initialize to distinct dummy values
std::size_t count1 = 0, count2 = 0;
for (int x : nums) {
// CRITICAL: Check equality with cand1 and cand2 FIRST before checking count == 0!
if (count1 > 0 && x == cand1) {
++count1;
} else if (count2 > 0 && x == cand2) {
++count2;
} else if (count1 == 0) {
cand1 = x;
count1 = 1;
} else if (count2 == 0) {
cand2 = x;
count2 = 1;
} else {
// 3 distinct elements {cand1, cand2, x} annihilate each other
--count1;
--count2;
}
}
// Pass 2: Verify exact counts of surviving candidates
std::size_t exact1 = 0, exact2 = 0;
for (int x : nums) {
if (count1 > 0 && x == cand1) ++exact1;
else if (count2 > 0 && x == cand2) ++exact2;
}
std::vector<int> result;
const std::size_t threshold = nums.size() / 3;
if (exact1 > threshold) result.push_back(cand1);
if (exact2 > threshold) result.push_back(cand2);
return result;
}
Two subtle bugs break majority-vote implementations in interviews and production:
- Skipping Pass 2 when majority is not guaranteed: On the input
[1, 1, 2, 2, 3], Boyer-Moore returnscandidate = 3withcount = 1, even though3appears only once ($20\%$ of the stream)! Pass 1 only guarantees: if a majority element exists, it is the survivor. Unless the problem statement mathematically guarantees a majority exists, Pass 2 is mandatory. - Checking
count2 == 0beforex == cand1in $k = 3$: If you checkif (count1 == 0) ... else if (count2 == 0)before checkingif (x == cand1), then on the input[5, 5, ...], the second5will be assigned tocand2, placing the same key into both slots! Always test equality against active candidates before claiming an empty slot.