DSA DSA In Depth
0%
lesson streamingmajority-voteheavy-hitters

Boyer-Moore Majority Vote & Misra-Gries Heavy Hitters

7 min read Module 1.2: Contiguous Arrays, Bit Manipulation & Pointer Idioms Updated Oct 23, 2026
📄 Cheat Sheet
Boyer-Moore Majority Vote Algorithm — O(1) Space Pairwise Cancellation
NeetCode Boyer-Moore Majority Vote Algorithm — O(1) Space Pairwise Cancellation Watch on YouTube ↗
0:00 / --:--

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$ as candidate = x and set count = 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!

Mathematical Proof: The Misra-Gries Frequency Lower Bound

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$)

Misra-Gries (k = 3): Track k - 1 = 2 Candidates. Incoming 3rd Distinct Key Triggers Triplet Annihilation! Candidate 1: Key A count_1 = 4 --> 3 Candidate 2: Key B count_2 = 2 --> 1 Incoming: Key C (C != A and C != B) Annihilated 3-Tuple {A, B, C} Deleted! At most N / 3 triplet cancellations can occur across N items. Any item with true count > N / 3 survives! Critical Rule: Always check (x == cand1) and (x == cand2) BEFORE checking (count1 == 0) or (count2 == 0)!

Two-Pass Verification Pipeline for Heavy Hitters

flowchart LR A["Pass 1: Stream N items
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"]
Hardware Reality: Register-Pinned Counters & Distributed Tree-AllReduce Merging

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.

Boyer-Moore Majority Pairing Cancellation & Misra-Gries Heavy Hitters
O(N) Time · O(1) / O(k) Space

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.

Step 1 / 7
Custom Input:
Timeline

Invariant & State

Every decrement pairs 1 copy of candidate with 1 distinct non-candidate. Since > N/2 copies of A exist, at least 1 copy of A cannot be paired and survives!
📊 Live Magnitude & Pointer Stage — Boyer-Moore Majority Pairing Cancellation & Misra-Gries Heavy Hitters Active Pivot Match/Sorted
A (can… [0] count=2 A (c=2) [1] Pair (A B (can… [2] A (c=2) [3] Pair (A C (can… [4] Pair (A B (can… [5] Survivo A (can… [6]
[0]A (cand=A, c=1)
[1]A (c=2)count=2
[2]B (cancel→c=1)Pair (A,B)
[3]A (c=2)
[4]C (cancel→c=1)Pair (A,C)
[5]B (cancel→c=0)Pair (A,B)
[6]A (cand=A, c=1)Survivor = A!
Pairs Cancelled
3 pairs (6 elements)
Surviving Candidate
A (count = 1)
Pass 2 Verification
freq(A) = 4 > 7/2
[Step 1] Cancelled 3 distinct pairs (A,B), (A,C), (A,B); final survivor A is verified as majority element.

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;
}
Production Pitfall: Omitting Pass 2 Verification & Duplicate Candidate Adoption in $k=3$

Two subtle bugs break majority-vote implementations in interviews and production:

  1. Skipping Pass 2 when majority is not guaranteed: On the input [1, 1, 2, 2, 3], Boyer-Moore returns candidate = 3 with count = 1, even though 3 appears 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.
  2. Checking count2 == 0 before x == cand1 in $k = 3$: If you check if (count1 == 0) ... else if (count2 == 0) before checking if (x == cand1), then on the input [5, 5, ...], the second 5 will be assigned to cand2, placing the same key into both slots! Always test equality against active candidates before claiming an empty slot.
Check yourself Question 1 of 4
Concept Check Why Boyer-Moore Majority Vote Requires a Second Pass When Majority Is Unproven recall

You run Pass 1 of the Boyer-Moore Majority Vote algorithm on the array `nums = [1, 1, 2, 2, 3]`. At termination, `candidate == 3` and `count == 1`.

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

How can the 1,000 worker nodes combine their local Boyer-Moore `(cand_i, count_i)` results in a single AllReduce pass without shuffling the raw data?

Key Takeaways

Boyer-Moore Majority Vote pairs each majority occurrence against a distinct element, finding the > N/2 candidate in O(N) time and O(1) space.
Misra-Gries generalizes pairwise cancellation to k-tuple cancellation using k - 1 counters, guaranteeing f(x) - N/k <= f_hat(x) <= f(x).
Always execute a second linear verification pass when the input is not guaranteed to contain a heavy hitter exceeding N/k.
In multi-candidate Misra-Gries (such as k = 3), always check if the incoming element matches an existing active candidate before assigning it to a zero-count slot.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue