Skip to content

Commit eb8bb4b

Browse files
committed
solve Majority Element II
1 parent 49e2318 commit eb8bb4b

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed

problems/majority_element_ii.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from typing import List
2+
3+
4+
class OfficialSolution:
5+
"""
6+
== Approach 1: Boyer-Moore Voting Algorithm ==
7+
8+
== Intuition ==
9+
To figure out a O(1) space requirement, we would need to get this simple intuition
10+
first. For an array of length n:
11+
- There can be at most one majority element, which is more than floor(n/2)
12+
times.
13+
- There can be at most two majority elements, which are more than floor(n/3)
14+
times.
15+
- There can be at most three majority elements, which are more than floor(n/4)
16+
times.
17+
and so on.
18+
19+
Knowing this can help us understand how we can keep track of majority elements which
20+
satisfies O(1) space requirement.
21+
22+
Let's try to get an intuition for the case where we would like to find a majority
23+
element which is more than floor(n/2) times in an array of length n.
24+
25+
The idea is to have two variables, one holding a potential candidate for majority
26+
element and a counter to keep track of whether to swap a potential candidate or not.
27+
Why can we get away with only 2 variables? Because there can be at most 1 majority
28+
element which is more than floor(n/2) times. Therefore, having only one variable to
29+
hold the only potential candidate and one counter is enough.
30+
31+
While scanning the array, the counter is incremented if you encounter an element
32+
which is exactly same as the potential candidate but decremented otherwise. When the
33+
counter reaches 0, the element which will be encountered next will become the
34+
potential candidate. Keep doing this procedure while scanning the array. However,
35+
when you have exhausted the array, you have to make sure that the element recorded
36+
in the potential candidate variable is the majority element by checking whether it
37+
occurs more than floor(n/2) times in the array. In the original Majority Element
38+
problem, it is guaranteed that there is a majority element in the array so your
39+
implementation can omit the second pass. However, in a general case, you need this
40+
second pass since your array can have no majority elements at all!
41+
42+
The counter is initialized as 0 and the potential candidate as None at the start of
43+
the array.
44+
45+
If an element is truly a majority element, it will stick in the potential candidate
46+
variable, no matter how it shows up in the array (i.e. all clustered in the
47+
beginning of the array, all clustered near the end of the array, or showing up
48+
anywhere in the array), after the whole array has been scanned. Of course, while you
49+
are scanning the array, the element might be replaced by another element in the
50+
process, but the true majority element will definitely remain as the potential
51+
candidate in the end.
52+
53+
The above is essentially the Boyer-Moore algorithm we encountered in the Majority
54+
Element problem.
55+
56+
Now figuring out the majority elements which show up more than floor(n/3) times is
57+
not that hard anymore. Using the intuition presented in the beginning, we only need
58+
4 variables: 2 for holding two potential candidates and 2 for holding the 2
59+
corresponding counters. Similar to the above case, both candidates are initialized
60+
as None in the beginning with their corresponding counters being 0. While going
61+
through the array:
62+
- If the current element is equal to one of the potential candidate, the count for
63+
that candidate is increased while leaving the count of the other candidate as it is.
64+
- If the counter reaches 0, the candidate associated with that counter will be
65+
replaced with the next element if the next element is not equal to the other
66+
candidate as well.
67+
- Both counters are decremented only when the current element is different from
68+
both candidates.
69+
70+
== Complexity Analysis ==
71+
Time Complexity: O(N) where N is the size of nums. We first go through nums looking
72+
for first and second potential candidates. We then count the number of
73+
occurrences for these two potential candidates in nums. Therefore, our runtime
74+
is O(N) + O(N) = O(2N) = O(N).
75+
Space Complexity: O(1) since we only have 4 variables for holding 2 potential
76+
candidates and 2 counters. Even the returning array is at most 2 elements.
77+
"""
78+
79+
def majorityElement(self, nums: List[int]) -> List[int]:
80+
majority_one = majority_two = None
81+
count_one = count_two = 0
82+
for num in nums:
83+
if count_one == 0 and majority_two != num:
84+
majority_one = num
85+
86+
if count_two == 0 and majority_one != num:
87+
majority_two = num
88+
89+
if num == majority_one:
90+
count_one += 1
91+
elif num == majority_two:
92+
count_two += 1
93+
else:
94+
count_one -= 1
95+
count_two -= 1
96+
97+
ans = []
98+
cutoff = len(nums) // 3
99+
100+
# verify majority_one is indeed one of the answers
101+
for majority_candidate in (majority_one, majority_two):
102+
if nums.count(majority_candidate) > cutoff:
103+
ans.append(majority_candidate)
104+
105+
return ans

tests/test_majority_element_ii.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import unittest
2+
3+
from majority_element_ii import OfficialSolution
4+
5+
6+
class TestMajorityElementII(unittest.TestCase):
7+
def test_example_1(self):
8+
assert OfficialSolution().majorityElement(nums=[3, 2, 3]) == [3]
9+
10+
def test_example_2(self):
11+
assert OfficialSolution().majorityElement(nums=[1, 1, 1, 3, 3, 2, 2, 2]) == [
12+
1,
13+
2,
14+
]
15+
16+
def test_example_3(self):
17+
assert OfficialSolution().majorityElement(nums=[0, -1, 2, -1]) == [-1]
18+
19+
def test_example_4(self):
20+
assert OfficialSolution().majorityElement(nums=[1, 2, 3, 4]) == []

0 commit comments

Comments
 (0)