229. Majority Element II
Problem Description
This problem asks you to find all elements in an integer array that appear more than βn/3β
times, where n
is the size of the array and βn/3β
represents the floor division (rounding down to the nearest integer).
For example:
- If the array has 9 elements, you need to find elements appearing more than
β9/3β = 3
times - If the array has 10 elements, you need to find elements appearing more than
β10/3β = 3
times - If the array has 11 elements, you need to find elements appearing more than
β11/3β = 3
times
The key insight is that there can be at most 2 elements that appear more than βn/3β
times in any array. This is because if 3 elements each appeared more than βn/3β
times, their combined count would exceed n
, which is impossible.
The solution uses the Boyer-Moore Majority Vote algorithm extended for finding elements that appear more than n/3
times. It maintains two potential candidates (m1
and m2
) with their respective counts (n1
and n2
). The algorithm processes each element by:
- Incrementing the count if it matches a candidate
- Setting it as a new candidate if a candidate slot is empty (count is 0)
- Decrementing both counts if neither condition is met
After the first pass identifies potential candidates, a second pass verifies which candidates actually appear more than βn/3β
times in the array.
Intuition
The key observation is that at most 2 elements can appear more than n/3
times in an array. Why? If 3 or more elements each appeared more than n/3
times, their total count would exceed n
, which is impossible.
This constraint leads us to think about tracking just 2 potential candidates. But how do we find these candidates efficiently without using extra space for counting?
The idea comes from the Boyer-Moore Voting Algorithm, which can find a majority element (appearing more than n/2
times) in one pass. The algorithm works by maintaining a candidate and a counter - when we see the candidate, we increment the counter; when we see a different element, we decrement it. When the counter reaches zero, we switch to a new candidate.
For our problem, we extend this to track 2 candidates simultaneously. The intuition is based on a "pairing and cancellation" strategy:
- When we see an element matching one of our candidates, we strengthen that candidate's position by incrementing its count
- When we see a new element and have an empty candidate slot (count = 0), we add it as a potential candidate
- When we see an element that doesn't match either candidate and both slots are occupied, we decrement both counts
Why does this work? Think of it as a survival game where elements compete for dominance. Elements appearing more than n/3
times are so frequent that even if they lose some "votes" to cancellation with other elements, they'll still survive as one of the final two candidates.
The cancellation step (decrementing both counts) essentially removes 3 different elements from consideration - one of each candidate type and the current element. Since any element appearing more than n/3
times must survive this repeated removal of triplets, it will end up as one of our candidates.
Finally, we need a verification pass because our candidates might include elements that don't actually appear more than n/3
times (they just survived the cancellation process). So we count their actual occurrences to confirm.
Solution Approach
The implementation uses the Boyer-Moore Majority Vote algorithm extended for finding up to 2 majority elements. Let's walk through the code step by step:
Initialization:
n1 = n2 = 0 # Counters for the two candidates m1, m2 = 0, 1 # Two different initial candidates
We initialize two counters n1
and n2
to 0, and two candidates m1
and m2
to different values (0 and 1) to ensure they start as distinct.
First Pass - Finding Candidates:
for m in nums: if m == m1: n1 += 1 elif m == m2: n2 += 1 elif n1 == 0: m1, n1 = m, 1 elif n2 == 0: m2, n2 = m, 1 else: n1, n2 = n1 - 1, n2 - 1
For each element m
in the array, we follow this decision tree:
- If
m
matches candidate 1 (m1
): Increment countern1
- Else if
m
matches candidate 2 (m2
): Increment countern2
- Else if counter 1 is zero (
n1 == 0
): Replacem1
with current elementm
and setn1 = 1
- Else if counter 2 is zero (
n2 == 0
): Replacem2
with current elementm
and setn2 = 1
- Otherwise: Decrement both counters (
n1
andn2
) by 1
The order of these conditions is crucial. We first check if the current element matches existing candidates before considering replacement or cancellation.
Second Pass - Verification:
return [m for m in [m1, m2] if nums.count(m) > len(nums) // 3]
After identifying the two potential candidates, we verify which ones actually appear more than n/3
times. We use list comprehension to:
- Iterate through both candidates
[m1, m2]
- Count each candidate's actual occurrences using
nums.count(m)
- Include only those with count greater than
len(nums) // 3
Time and Space Complexity:
- Time:
O(n)
for the first pass +O(n)
for each verification =O(n)
- Space:
O(1)
as we only use a constant amount of extra variables
The algorithm elegantly handles edge cases like arrays with fewer than 3 elements or arrays where no element appears more than n/3
times, returning an appropriate result in each case.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with the array [3, 2, 3, 1, 3, 2, 1, 1]
(n = 8, so we need elements appearing more than 8/3 = 2 times).
Initial State:
m1 = 0, n1 = 0
(candidate 1 and its counter)m2 = 1, n2 = 0
(candidate 2 and its counter)
First Pass - Finding Candidates:
Step | Current Element | Action | m1 | n1 | m2 | n2 | Explanation |
---|---|---|---|---|---|---|---|
1 | 3 | n1 == 0, set m1 = 3 | 3 | 1 | 1 | 0 | First candidate slot empty, use it |
2 | 2 | n2 == 0, set m2 = 2 | 3 | 1 | 2 | 1 | Second candidate slot empty, use it |
3 | 3 | m == m1, increment n1 | 3 | 2 | 2 | 1 | Matches candidate 1 |
4 | 1 | Decrement both | 3 | 1 | 2 | 0 | Doesn't match either candidate |
5 | 3 | m == m1, increment n1 | 3 | 2 | 2 | 0 | Matches candidate 1 |
6 | 2 | n2 == 0, set m2 = 2 | 3 | 2 | 2 | 1 | Second slot empty (n2 was 0) |
7 | 1 | Decrement both | 3 | 1 | 2 | 0 | Doesn't match either candidate |
8 | 1 | n2 == 0, set m2 = 1 | 3 | 1 | 1 | 1 | Second slot empty (n2 was 0) |
After First Pass:
- Candidate 1:
m1 = 3
withn1 = 1
- Candidate 2:
m2 = 1
withn2 = 1
Second Pass - Verification: Now we count actual occurrences:
- Count of 3: appears 3 times in
[3, 2, 3, 1, 3, 2, 1, 1]
β 3 > 2 β - Count of 1: appears 3 times in
[3, 2, 3, 1, 3, 2, 1, 1]
β 3 > 2 β
Result: [3, 1]
(both elements appear more than β8/3β = 2 times)
The algorithm correctly identified both majority elements. Notice how element 2 (appearing only 2 times) was eliminated through the cancellation process, while elements 3 and 1 (each appearing 3 times) survived as candidates and passed verification.
Solution Implementation
1class Solution:
2 def majorityElement(self, nums: List[int]) -> List[int]:
3 # Boyer-Moore Voting Algorithm for finding elements that appear more than n/3 times
4 # We can have at most 2 majority elements that appear more than n/3 times
5
6 # Initialize two potential candidates and their counts
7 candidate1, candidate2 = 0, 1 # Different initial values to distinguish them
8 count1, count2 = 0, 0
9
10 # First pass: Find potential candidates using modified Boyer-Moore algorithm
11 for num in nums:
12 if num == candidate1:
13 # Current number matches candidate1, increment its count
14 count1 += 1
15 elif num == candidate2:
16 # Current number matches candidate2, increment its count
17 count2 += 1
18 elif count1 == 0:
19 # No votes for candidate1, replace it with current number
20 candidate1 = num
21 count1 = 1
22 elif count2 == 0:
23 # No votes for candidate2, replace it with current number
24 candidate2 = num
25 count2 = 1
26 else:
27 # Current number doesn't match either candidate and both have votes
28 # Decrement both counts (voting against both candidates)
29 count1 -= 1
30 count2 -= 1
31
32 # Second pass: Verify that candidates actually appear more than n/3 times
33 threshold = len(nums) // 3
34 result = []
35
36 for candidate in [candidate1, candidate2]:
37 if nums.count(candidate) > threshold:
38 result.append(candidate)
39
40 return result
41
1class Solution {
2 public List<Integer> majorityElement(int[] nums) {
3 // Initialize counters and candidates for two potential majority elements
4 // Using Boyer-Moore Voting Algorithm extended for finding elements appearing more than n/3 times
5 int count1 = 0, count2 = 0;
6 int candidate1 = 0, candidate2 = 1; // Initialize with different values to avoid conflicts
7
8 // First pass: Find potential candidates
9 for (int num : nums) {
10 if (num == candidate1) {
11 // Current number matches candidate1, increment its count
12 count1++;
13 } else if (num == candidate2) {
14 // Current number matches candidate2, increment its count
15 count2++;
16 } else if (count1 == 0) {
17 // candidate1's count is 0, replace it with current number
18 candidate1 = num;
19 count1++;
20 } else if (count2 == 0) {
21 // candidate2's count is 0, replace it with current number
22 candidate2 = num;
23 count2++;
24 } else {
25 // Current number doesn't match either candidate and both have non-zero counts
26 // Decrement both counts (elimination step)
27 count1--;
28 count2--;
29 }
30 }
31
32 // Second pass: Verify if candidates actually appear more than n/3 times
33 List<Integer> result = new ArrayList<>();
34 count1 = 0;
35 count2 = 0;
36
37 // Count actual occurrences of both candidates
38 for (int num : nums) {
39 if (num == candidate1) {
40 count1++;
41 } else if (num == candidate2) {
42 count2++;
43 }
44 }
45
46 // Check if candidate1 appears more than n/3 times
47 if (count1 > nums.length / 3) {
48 result.add(candidate1);
49 }
50
51 // Check if candidate2 appears more than n/3 times
52 if (count2 > nums.length / 3) {
53 result.add(candidate2);
54 }
55
56 return result;
57 }
58}
59
1class Solution {
2public:
3 vector<int> majorityElement(vector<int>& nums) {
4 // Boyer-Moore Voting Algorithm for finding elements appearing more than n/3 times
5 // At most 2 elements can appear more than n/3 times in an array
6
7 // candidate1 and candidate2 store potential majority elements
8 int candidate1 = 0, candidate2 = 0;
9 // count1 and count2 track the counts for each candidate
10 int count1 = 0, count2 = 1; // Initialize count2 to 1 to ensure candidates are different initially
11
12 // Phase 1: Find potential candidates using modified Boyer-Moore algorithm
13 for (int num : nums) {
14 // If current number matches candidate1, increment its count
15 if (num == candidate1) {
16 ++count1;
17 }
18 // If current number matches candidate2, increment its count
19 else if (num == candidate2) {
20 ++count2;
21 }
22 // If count1 is 0, assign current number as new candidate1
23 else if (count1 == 0) {
24 candidate1 = num;
25 ++count1;
26 }
27 // If count2 is 0, assign current number as new candidate2
28 else if (count2 == 0) {
29 candidate2 = num;
30 ++count2;
31 }
32 // If current number doesn't match either candidate and both counts are non-zero,
33 // decrement both counts (this is the "voting out" step)
34 else {
35 --count1;
36 --count2;
37 }
38 }
39
40 // Phase 2: Verify that candidates actually appear more than n/3 times
41 vector<int> result;
42 int threshold = nums.size() / 3;
43
44 // Check if candidate1 appears more than n/3 times
45 if (count(nums.begin(), nums.end(), candidate1) > threshold) {
46 result.push_back(candidate1);
47 }
48
49 // Check if candidate2 appears more than n/3 times
50 if (count(nums.begin(), nums.end(), candidate2) > threshold) {
51 result.push_back(candidate2);
52 }
53
54 return result;
55 }
56};
57
1function majorityElement(nums: number[]): number[] {
2 // Boyer-Moore Voting Algorithm for finding elements appearing more than n/3 times
3 // At most 2 elements can appear more than n/3 times in an array
4
5 // candidate1 and candidate2 store potential majority elements
6 let candidate1: number = 0;
7 let candidate2: number = 0;
8 // count1 and count2 track the counts for each candidate
9 let count1: number = 0;
10 let count2: number = 1; // Initialize count2 to 1 to ensure candidates are different initially
11
12 // Phase 1: Find potential candidates using modified Boyer-Moore algorithm
13 for (const num of nums) {
14 // If current number matches candidate1, increment its count
15 if (num === candidate1) {
16 count1++;
17 }
18 // If current number matches candidate2, increment its count
19 else if (num === candidate2) {
20 count2++;
21 }
22 // If count1 is 0, assign current number as new candidate1
23 else if (count1 === 0) {
24 candidate1 = num;
25 count1++;
26 }
27 // If count2 is 0, assign current number as new candidate2
28 else if (count2 === 0) {
29 candidate2 = num;
30 count2++;
31 }
32 // If current number doesn't match either candidate and both counts are non-zero,
33 // decrement both counts (this is the "voting out" step)
34 else {
35 count1--;
36 count2--;
37 }
38 }
39
40 // Phase 2: Verify that candidates actually appear more than n/3 times
41 const result: number[] = [];
42 const threshold: number = Math.floor(nums.length / 3);
43
44 // Count occurrences of candidate1 in the array
45 let candidate1Count: number = 0;
46 for (const num of nums) {
47 if (num === candidate1) {
48 candidate1Count++;
49 }
50 }
51
52 // Check if candidate1 appears more than n/3 times
53 if (candidate1Count > threshold) {
54 result.push(candidate1);
55 }
56
57 // Count occurrences of candidate2 in the array
58 let candidate2Count: number = 0;
59 for (const num of nums) {
60 if (num === candidate2) {
61 candidate2Count++;
62 }
63 }
64
65 // Check if candidate2 appears more than n/3 times
66 if (candidate2Count > threshold) {
67 result.push(candidate2);
68 }
69
70 return result;
71}
72
Time and Space Complexity
Time Complexity: O(n)
The algorithm consists of two main parts:
- First pass through the array with the Boyer-Moore voting algorithm:
O(n)
, where each element is visited once with constant time operations (comparisons and arithmetic operations) - Second pass for verification using
nums.count()
: This callscount()
at most twice (form1
andm2
), and eachcount()
operation takesO(n)
time to scan through the entire array
Total time complexity: O(n) + 2 * O(n) = O(n)
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space:
- Four integer variables:
n1
,n2
,m1
,m2
- The output list contains at most 2 elements (since there can be at most 2 elements that appear more than
n/3
times in an array) - No additional data structures that scale with input size are used
The space complexity remains constant regardless of the input size, making it O(1)
.
Common Pitfalls
1. Duplicate Candidates in the Result
The most critical pitfall occurs when both candidates end up being the same value, leading to duplicates in the final result. This happens when the array contains only one majority element or when both candidate slots converge to the same value.
Problem Example:
nums = [3, 3, 3] # After first pass: candidate1 = 3, candidate2 = 3 # Result would be [3, 3] instead of [3]
Solution: Add a check to ensure candidates are distinct before verification:
# Second pass: Verify candidates
threshold = len(nums) // 3
result = []
# Check candidate1
if nums.count(candidate1) > threshold:
result.append(candidate1)
# Check candidate2 only if it's different from candidate1
if candidate1 != candidate2 and nums.count(candidate2) > threshold:
result.append(candidate2)
return result
2. Incorrect Initialization of Candidates
Initializing both candidates to the same value (like candidate1 = candidate2 = 0
) can cause problems when the array actually contains 0 as an element.
Problem Example:
# Wrong initialization candidate1 = candidate2 = 0 # Both same value nums = [0, 0, 0, 1, 2] # The algorithm may not correctly identify distinct candidates
Solution: Initialize candidates to different values or use None/float('inf'):
# Option 1: Different arbitrary values candidate1, candidate2 = 0, 1 # Option 2: Use None and check for it candidate1 = candidate2 = None # Then modify the conditions to check for None before comparing
3. Inefficient Verification Using count()
Using nums.count()
in the verification phase performs a full array scan for each candidate, which could be optimized.
Problem:
# This performs two full scans of the array for candidate in [candidate1, candidate2]: if nums.count(candidate) > threshold: # O(n) each time
Solution: Count both candidates in a single pass:
# Single pass verification
count1 = count2 = 0
threshold = len(nums) // 3
for num in nums:
if num == candidate1:
count1 += 1
elif num == candidate2:
count2 += 1
result = []
if count1 > threshold:
result.append(candidate1)
if count2 > threshold and candidate1 != candidate2:
result.append(candidate2)
4. Edge Case: Empty Array
Not handling empty arrays can cause errors when accessing candidates or calculating threshold.
Problem Example:
nums = [] # len(nums) // 3 = 0, but the algorithm might still try to process candidates
Solution: Add an early return for edge cases:
def majorityElement(self, nums: List[int]) -> List[int]:
if not nums:
return []
if len(nums) <= 2:
return list(set(nums)) # All elements appear more than n/3 times
# Continue with Boyer-Moore algorithm...
Which of the following is a min heap?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!