|
| 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 |
0 commit comments