@author: sdubrz
@date: 2020.04.03
题目难度: 简单
考察内容:分而治之
原题链接 https://leetcode-cn.com/problems/majority-element/
题目的著作权归领扣网络所有,商业转载请联系官方授权,非商业转载请注明出处。
解题代码转载请联系 lwyz521604#163.com
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [3,2,3]
输出: 3
示例 2:
输入: [2,2,1,1,1,2,2]
输出: 2
分治解法
LeetCode上把这道题分到了分治部分,所以我一上来考虑的是怎么用分治法来求解。这道题用分治法来求解的主要思路是从中间将数组分割成两个子数组,多数元素肯定在至少一个子数组上也是多数元素。这一点可以用反证法比较容易地进行证明。如果只有一个子数组有多数元素,就可以把这个多数元素当做整个数组的多数元素。如果两个子数组分别有不同的多数元素,则需要比较这两个多数元素在整个数组中的个数。最后划分到子数组只包括一个元素时,直接返回。这样总共需要划分log(n)
层,每一层的代价是O(n)
,因而分治法的总时间复杂度是O(nlogn)
其实,还有一些更好的方法。
用HashMap
可以使用HashMap方法统计每个数的个数,需要O(n)
的时间,然后再找出出现次数最多的数,也需要O(n)
的时间,因而HashMap的方法的时间复杂度为O(n)
。
先排序
最简单的一种方法就是对数据进行排序,然后第n/2个点一定是多数元素。
随机方法
这道题其实是一个典型的可以用随机方法来求解的方法。我们可以在O(n)
的时间内检查某个数是不是多数元素。基于此,我们可以每次随机地抽取一个数,然后判断这个数是不是多数元素,如果是,则返回。
由于数组中一定存在多数元素,因而可以通过数学证明,随机方法执行的次数的期望为常数级别。因而这个方法的期望复杂度为O(n)
。
Boyer-Moore投票算法
这是一种十分巧妙的方法,也是官方给出的最后一种解法。可以理解为是一种计数的方法。其实现代码如下
class Solution {
public int majorityElement(int[] nums) {
int count = 1;
int label = nums[0];
for(int i=1; i<nums.length; i++){
if(label==nums[i]){
count++;
}else{
count--;
if(count==0){
label = nums[i+1];
}
}
}
return label;
}
}
很显然,这个方法的时间复杂度是O(n)
的,只需要遍历一次数组,并且所需要的额外空间也是常数级别的,是一种非常理想的方法。LeetCode官方给出的解法中,对这个方法做了比较详细的说明。
可以通过一种比较简单的方式来理解这个算法。首先,有一点可以肯定的是,多数元素的个数肯定要多于其他的元素。因而,在整个数组中,如果我们用一个多数元素去抵消一个其他元素,最后肯定会有剩余的多数元素。
在这个方法中,如果我们一开始选择的数恰好就是正确的多数元素,很显然算法会直接返回正确多数元素结束。而如果开始选择的元素不是多数元素,那么从选择这个数为候选点开始,到count重新为0止,走的这段数组中,相当于用一个不为多数元素的数,抵消掉了等量的不等于它的数。因而,在剩下的部分的多数元素就是原数组的多数元素,因而可以继续循环,其实道理有点类似于递归了。
不得不佩服,这个方法确实巧妙。