Majority Element: Boyer-Moore Voting
Find the element appearing more than n/2 times using the linear-time, O(1)-space Boyer-Moore voting algorithm and prove its correctness.
Majority Element: Boyer-Moore Voting is a free DSA Interview Prep lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the DSA Interview Prep learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Majority Element Problem
Majority Element (LeetCode 169): find the element that appears more than n/2 times in an array of length n. The majority element always exists by the problem's guarantee. For [3, 2, 3], the answer is 3. For [2, 2, 1, 1, 1, 2, 2], the answer is 2 (appears 4 times out of 7). Approaches range from sorting in O(n log n) to the elegant O(n) O(1) Boyer-Moore voting algorithm.
# The majority element appears MORE than n/2 times
# So it appears more than all other elements COMBINED
examples = [
[3, 2, 3], # 3 appears 2/3 times > 1/2
[2, 2, 1, 1, 1, 2, 2], # 2 appears 4/7 times > 3.5
[1], # trivially 1
[1, 1, 2, 1], # 1 appears 3/4 times
]
for e in examples:
from collections import Counter
c = Counter(e)
print(f'Array: {e} → majority: {max(c, key=c.get)} (count {max(c.values())})')Approaches Before Boyer-Moore
Three approaches before the optimal: (1) Sorting: sort the array; the middle element is always the majority (since it appears >n/2 times). O(n log n), O(1) space. (2) Hash map: count frequencies and return the element with count > n/2. O(n) time, O(n) space. (3) Random sampling: pick a random element and verify it appears >n/2 times; expected O(1) trials (majority element is picked with probability >1/2). Boyer-Moore achieves O(n) time and O(1) space deterministically.
from collections import Counter
def majority_sort(nums):
nums.sort()
return nums[len(nums) // 2] # middle is always majority
def majority_hashmap(nums):
count = Counter(nums)
return max(count, key=count.get)
def majority_random(nums):
import random
n = len(nums)
while True:
candidate = random.choice(nums)
if nums.count(candidate) > n // 2:
return candidate
nums = [2, 2, 1, 1, 1, 2, 2]
print(majority_sort(nums[:])) # 2
print(majority_hashmap(nums)) # 2Boyer-Moore Voting Algorithm
The Boyer-Moore voting algorithm maintains a candidate and a count. Walk through the array: if count == 0, set the current element as the new candidate. If the current element matches the candidate, increment count. Otherwise, decrement count. At the end, the candidate is the majority element. This works because the majority element appears more than all others combined — it can never be completely voted out.
def majority_element(nums):
candidate = None
count = 0
for num in nums:
if count == 0:
candidate = num # new candidate
if num == candidate:
count += 1
else:
count -= 1
return candidate
print(majority_element([3, 2, 3])) # 3
print(majority_element([2, 2, 1, 1, 1, 2, 2])) # 2
print(majority_element([1])) # 1Intuition Behind the Algorithm
Intuition: imagine each element 'cancels' one occurrence of a different element. The majority element (count > n/2) has more occurrences than all others combined, so it can cancel all non-majority elements and still have remaining occurrences. The count variable tracks the net lead of the current candidate. When count hits 0, the current candidate has been cancelled by as many opposing elements — whoever emerges next is the new candidate.
def bm_trace(nums):
candidate = count = 0
for i, num in enumerate(nums):
if count == 0:
candidate = num
old_count = count
if num == candidate: count += 1
else: count -= 1
print(f'num={num}: candidate={candidate}, count: {old_count}→{count}')
return candidate
bm_trace([2, 2, 1, 1, 1, 2, 2])
# 2→c=1, 2→c=2, 1→c=1, 1→c=0, 1→new cand=1 c=1, 2→c=0, 2→new cand=2 c=1Correctness Proof
Proof: let m be the majority element with count k > n/2. At the end of the algorithm, can a non-majority element be the candidate? For that, m must have been cancelled completely. Each cancellation of m costs one occurrence of some other element. To cancel all k occurrences of m, you need at least k occurrences of non-m elements. But k > n/2 and non-m elements total n-k < n/2 < k. Contradiction — m cannot be fully cancelled.
# Proof by contradiction visualised:
# Array: [M, M, M, A, B, A, B] (M is majority, 4/7 times)
# Cancellations: M-A, M-B, M-A, M-B would need 4 non-M elements
# But there are only 4 non-M elements and 4 M's > n/2 = 3.5
# So M can survive: after cancellations, at least 1 M remains uncancelled
def verify_bm(tests):
for nums in tests:
result = majority_element(nums)
brute = max(set(nums), key=nums.count)
assert result == brute, f'Mismatch: {nums} → BM={result}, Brute={brute}'
print('All tests passed!')
def majority_element(nums):
c = cnt = 0
for n in nums:
if cnt == 0: c = n
cnt += 1 if n == c else -1
return c
verify_bm([[1],[3,2,3],[1,1,2,1],[2,2,1,1,1,2,2]])Majority Element II: More than n/3
Majority Element II (LeetCode 229): find all elements appearing more than n/3 times. At most 2 elements can satisfy this (since 3 × n/3 = n). Extend Boyer-Moore to maintain two candidates with two counts. When a new element matches neither candidate and both counts are positive, decrement both. A final verification pass confirms which candidates truly exceed n/3.
def majority_element_ii(nums):
cand1 = cand2 = None
count1 = count2 = 0
for num in nums:
if num == cand1: count1 += 1
elif num == cand2: count2 += 1
elif count1 == 0: cand1, count1 = num, 1
elif count2 == 0: cand2, count2 = num, 1
else:
count1 -= 1
count2 -= 1
# Verify: candidates must exceed n/3
n = len(nums)
return [c for c in [cand1, cand2]
if c is not None and nums.count(c) > n // 3]
print(majority_element_ii([3, 2, 3])) # [3]
print(majority_element_ii([1, 2])) # [1, 2]
print(majority_element_ii([1, 1, 1, 3, 3, 2, 2, 2])) # [1, 2]Generalised Boyer-Moore: n/k Majority
Boyer-Moore generalises to find all elements appearing more than n/k times using k-1 candidates. At most k-1 elements can satisfy this condition. Maintain k-1 (candidate, count) pairs. When none match and all counts are positive, decrement all counts by 1. This generalised algorithm runs in O(n) time and O(k) space. In interviews, knowing the two-candidate (n/3) extension is usually sufficient.
def majority_nk(nums, k):
'''Find all elements appearing more than n/k times.'''
counts = {} # candidate -> count
for num in nums:
counts[num] = counts.get(num, 0) + 1
if len(counts) >= k:
# Remove all candidates by decrementing
new_counts = {c: cnt-1 for c, cnt in counts.items() if cnt > 1}
counts = new_counts
# Verify
threshold = len(nums) // k
return [c for c in counts if nums.count(c) > threshold]
print(majority_nk([1,2,3,1,2,1,2,1], 3)) # [1, 2] (both > 8/3 ≈ 2.67)
print(majority_nk([1,1,1,2,2,3,3,3], 4)) # [1, 3] (both > 8/4 = 2)Divide and Conquer Majority Element
A D&C approach: split the array in half. The majority element of the full array must be a majority in at least one half (if it's the majority in neither, it can't appear more than n/2 times overall). Recursively find the majority of each half. If both halves agree, that's the answer. Otherwise, count both candidates across the full array and return the one with more occurrences. Recurrence: T(n) = 2T(n/2) + O(n) → O(n log n).
def majority_dc(nums, lo=None, hi=None):
if lo is None: lo, hi = 0, len(nums) - 1
if lo == hi: return nums[lo]
mid = (lo + hi) // 2
left_maj = majority_dc(nums, lo, mid)
right_maj = majority_dc(nums, mid + 1, hi)
if left_maj == right_maj:
return left_maj
# Count both candidates across the sub-range
left_count = sum(1 for i in range(lo, hi+1) if nums[i] == left_maj)
right_count = sum(1 for i in range(lo, hi+1) if nums[i] == right_maj)
return left_maj if left_count > right_count else right_maj
print(majority_dc([3, 2, 3])) # 3
print(majority_dc([2, 2, 1, 1, 1, 2, 2])) # 2Boyer-Moore vs Other Methods
Method comparison for Majority Element: Sort: O(n log n) time, O(1) space, destructive. Hash map: O(n) time, O(n) space, non-destructive. D&C: O(n log n) time, O(log n) call stack space. Boyer-Moore: O(n) time, O(1) space, single pass, non-destructive. Boyer-Moore is strictly superior for this problem. Always lead with Boyer-Moore in interviews after briefly mentioning the easier hash map approach.
import time, random
nums = [random.randint(1, 100) for _ in range(500000)]
# Make element 42 the majority
nums = [42] * 300000 + nums[:200000]
random.shuffle(nums)
start = time.time()
from collections import Counter
hm = Counter(nums).most_common(1)[0][0]
print(f'HashMap: {hm} in {time.time()-start:.4f}s')
def bm(nums):
c = cnt = 0
for n in nums:
if cnt == 0: c = n
cnt += 1 if n == c else -1
return c
start = time.time()
result = bm(nums)
print(f'Boyer-Moore: {result} in {time.time()-start:.4f}s')
print(f'Both correct: {hm == result}')When No Majority is Guaranteed
Boyer-Moore always returns a candidate, but it may not be a majority element if none exists. If the problem does not guarantee a majority element, you must verify: after Boyer-Moore, count the candidate's occurrences. If count > n/2, it's the majority. Otherwise, return -1 or None. This verification adds another O(n) pass but keeps the overall algorithm O(n) time O(1) space.
def majority_element_safe(nums):
'''Returns majority element or None if it doesn't exist.'''
# Phase 1: find candidate
candidate = count = 0
for num in nums:
if count == 0:
candidate = num
count += 1 if num == candidate else -1
# Phase 2: verify
if nums.count(candidate) > len(nums) // 2:
return candidate
return None
print(majority_element_safe([3, 2, 3])) # 3 (majority exists)
print(majority_element_safe([1, 2, 3])) # None (no majority)
print(majority_element_safe([1, 2, 1, 2])) # None (tie, neither > n/2)Interview Walkthrough
Interview approach for Majority Element: (1) Mention sorting (O(n log n), O(1)) and hash map (O(n), O(n)) as initial approaches. (2) Introduce Boyer-Moore as the optimal O(n) O(1) solution. (3) Explain the cancel-out intuition: majority can't be cancelled because it has more occurrences than all others combined. (4) Code it cleanly in 5 lines. (5) Handle the edge case: if majority is not guaranteed, add a verification pass. This structure shows systematic thinking under time pressure.
# Clean 5-line Boyer-Moore for interviews
def majority_element(nums):
c, cnt = nums[0], 1
for n in nums[1:]:
cnt += (1 if n == c else -1)
if cnt == 0: c, cnt = n, 1
return c
# Verification (if majority not guaranteed)
def majority_with_check(nums):
c = majority_element(nums)
return c if nums.count(c) > len(nums) // 2 else -1
print(majority_element([3, 2, 3])) # 3
print(majority_element([2, 2, 1, 1, 1, 2, 2])) # 2
print('Time: O(n), Space: O(1)')Quick Check
Test your understanding of Data Structures & Algorithms — Coding Interview Prep concepts from this lesson.
Lesson Recap
In this lesson you learned: Boyer-Moore voting finds the majority element in O(n) time O(1) space using a candidate and count that cancel out non-majority elements, the algorithm extends to n/3 majority with two candidates and requires a verification pass when majority is not guaranteed, and the proof relies on the fact that the majority element has more occurrences than all other elements combined, making complete cancellation impossible. Next up we tackle the Median of Two Sorted Arrays using binary search on the partition boundary.
Frequently asked questions
Is the “Majority Element: Boyer-Moore Voting” lesson free?
Yes — the full text of “Majority Element: Boyer-Moore Voting” is free to read here on the web, and the DSA Interview Prep course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the DSA Interview Prep course, upgrade to CoddyKit PRO.
What will I learn in “Majority Element: Boyer-Moore Voting”?
Find the element appearing more than n/2 times using the linear-time, O(1)-space Boyer-Moore voting algorithm and prove its correctness. You practise DSA Interview Prep with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start DSA Interview Prep?
No prior experience is required. DSA Interview Prep on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Majority Element: Boyer-Moore Voting” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this DSA Interview Prep lesson?
Yes. Every DSA Interview Prep lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Divide and Conquer Template
- Count Inversions Using Modified Merge Sort
- Majority Element: Boyer-Moore Voting
- Median of Two Sorted Arrays