Facebook Pixel

229. Majority Element II

MediumArrayHash TableCountingSorting
Leetcode Link

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:

  1. Incrementing the count if it matches a candidate
  2. Setting it as a new candidate if a candidate slot is empty (count is 0)
  3. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. If m matches candidate 1 (m1): Increment counter n1
  2. Else if m matches candidate 2 (m2): Increment counter n2
  3. Else if counter 1 is zero (n1 == 0): Replace m1 with current element m and set n1 = 1
  4. Else if counter 2 is zero (n2 == 0): Replace m2 with current element m and set n2 = 1
  5. Otherwise: Decrement both counters (n1 and n2) 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 Evaluator

Example 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:

StepCurrent ElementActionm1n1m2n2Explanation
13n1 == 0, set m1 = 33110First candidate slot empty, use it
22n2 == 0, set m2 = 23121Second candidate slot empty, use it
33m == m1, increment n13221Matches candidate 1
41Decrement both3120Doesn't match either candidate
53m == m1, increment n13220Matches candidate 1
62n2 == 0, set m2 = 23221Second slot empty (n2 was 0)
71Decrement both3120Doesn't match either candidate
81n2 == 0, set m2 = 13111Second slot empty (n2 was 0)

After First Pass:

  • Candidate 1: m1 = 3 with n1 = 1
  • Candidate 2: m2 = 1 with n2 = 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 calls count() at most twice (for m1 and m2), and each count() operation takes O(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...
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of the following is a min heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More