题意描述:
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
注意 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
总结: