題意描述:
數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。你可以假設數組是非空的,並且給定的數組總是存在多數元素。
注意 1 <= 數組長度 <= 50000
示例:
輸入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
輸出: 2
解題思路:
Alice: 這道題好面熟啊。
Bob: 那可定是之前做過了,有什麼想法嗎 ?
Alice: 可以用Python
裏面的字典計算每個數字出現的次數,然後找到出現次數最多的那個就好了,時間複雜度應該是O(n)
, 空間複雜度是O(k) k
是不同數字的個數。
Bob: 還可以先排序,然後檢查相鄰相同元素的個數,找到最大的那個,時間複雜度應該是O(nlogn + n)
空間複雜度是O(1)
Alice: 我怎麼總想起來快速排序呢 ?能借鑑裏面的做法嗎,出現次數超過數組長度一半。那排好序後這些元素可定會覆蓋數組中間的位置啊。
Bob: 那就是說,排序後直接返回 nums.length / 2
位置的元素就行了 ?
Alice: 是啊,這樣時間複雜度就下降到O(n * log n)
了。哦哦哦,我想起來,還有一種算法,是O(n)
的時間複雜度,O(1)
的空間複雜度。
Bob: 我也想起來了,好像叫什麼投票算法。不過我自己把它叫做 “羣雄逐鹿” 算法。 想象一下,東漢末年,羣雄並起,有一個兒數組,數組中的不同的元素表示不同的派系,相鄰位置的元素之間戰爭不斷,只有相鄰且相同的元素才能共存,相鄰不相同的元素只能同歸於盡,問最後誰能問鼎中原呢 ?
Alice: hhh, 你這不就是羣毆嗎,只有相鄰且相同的元素才能積蓄,相鄰不相同的元素相互抵消。這樣從前到後遍歷數組,最後留下來的就多數,而且是數量最多的。你發現了沒有,這個投票算法不僅僅可以求數量超過數組元素個數一般的,也可以求更一般的結果,可以求數組中相同數字最多的那個。
Bob: 👍👍😎😎
Alice: 還是不會用 Java 中的字典呀 ,🤷♀️
代碼:
Python 方法一: 字典計數,然後求出現次數最多的。
class Solution:
def majorityElement(self, nums: List[int]) -> int:
cnt = {}
for x in nums:
if x not in cnt:
cnt[x] = 1
else:
cnt[x] += 1
maxCnt = 0
ret = 0
for x in cnt.keys():
if cnt[x] > maxCnt:
maxCnt = cnt[x]
ret = x
return ret
Java 方法二: 排序 + 檢查相鄰元素。
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
int ret = nums[0];
int cnt = 1;
int maxCnt = 1;
for(int i=1; i<nums.length; ++i){
if(nums[i-1] == nums[i]){
cnt += 1;
}else{
if(cnt > maxCnt){
maxCnt = cnt;
ret = nums[i-1];
}
cnt = 1;
}
}
if(cnt > maxCnt){
maxCnt = cnt;
ret = nums[nums.length-1];
}
return ret;
}
}
Java 方法二: 多於數組一半的元素連起來一定會覆蓋數組中間的那個位置,也就是排序後nums.length / 2
的位置
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length / 2];
}
}
Java 方法三: “功過相抵,逐鹿中原” ?
class Solution {
public int majorityElement(int[] nums) {
int cnt = 1;
int pre = nums[0];
for(int i=1; i<nums.length; ++i){
if(cnt == 0){ // 新的一輪爭霸
cnt = 1;
pre = nums[i];
continue;
}
if(nums[i] == pre){
cnt += 1; // "招兵"
}else{
cnt -= 1; // “打仗”
}
}
return pre;
}
}
易錯點:
- 一些測試點:
[1,2,3,2,2,2,5,4,2]
[1]
[1,1]
[1,2,1]
[2,3,3]
2
1
1
1
3
總結: