LintCode 主元素
主元素
给定一个整型数组,找出主元素,它在数组中的出现次数严格大于数组元素个数的二分之一。
给出数组[1,1,1,1,2,2,2],返回 1
挑战
要求时间复杂度为O(n),空间复杂度为O(1) 。
solution:
对于这个问题,有很多解决的方法。
Method1. 最基本的解决方法
使用两个for循环计算出每个元素出现的次数, 如果该次数大于数组元素的二分之一就返回该元素, 及伪代码为:
//arr[]
n = arr.length; //数组元素的个数
for i = arr[0] to arr[n-1]
count = 0
for (j = arr[0] to arr[n-1]
if (arr[i] == arr[j] and i != j)
++count;
if count > size/2
return arr[i]
对于该方法,其时间复杂度为 O(n2) , 空间空间复杂度为 O(1) 。
Method2. 排序
很明显,题目中的意思是该元素一定存在。因此,可以对数组进行排序。那么arr[size/2]一定是该主元素。
/**
* @param nums: a list of integers
* @return: find a majority number
*/
public int majorityNumber(ArrayList<Integer> nums) {
// write your code
Collections.sort(nums);
return nums.get(nums.size()/2);
}
注意:该方法只有当主元素一定存在时才有效。
如果使用快速排序对该数组进行原址排序那么其时间复杂度为
O(nlogn)
, 空间复杂度为
O(1)
。
Method3. 使用hashmap
使用HashMap来计算,其中key用来存储元素,而value表示元素出现的次数。
public static int majorityNumber(ArrayList<Integer> nums) {
Map<Integer, Integer> map = new HashMap<>();
for (int e : nums) {
Integer count = map.get(e);
count = (count == null ? 1 : count+1);
if (count > nums.size()/2)
return e;
map.put(e, count);
}
return -1;
}
很显然写这篇博客的原因,并不是简简单单的为了解决该问题。而是为了学习。在题目中有个挑战:要求时间复杂度为O(n),空间复杂度为O(1) 。因此,Google时发现了Moore’s Voting Algorithm,该算法可以在
O(n)
解决该问题。
因此,以下内容翻译自:GeeksforGeeks Majority Element
该博客中还提到了一种解决该问题的方法,使用二叉查找树。
Method4. 二叉查找树
其中二叉查找树的数据域如下:
struct tree
{
int element;
int count;
}BST;
对于每一个元素都保存了一个计数器,用来计数每个元素出现的次数。
因此,对于数组中的元素,一个一个的插入到二叉树中。如果该元素已经存在二叉树中则计数器加一。并且判断该计数器是否大于size/2,如果是则返回该元素,否则继续插入元素。
其中,该方法的最好情况是所有的主元素都在数组中开头的位置。例如:{1, 1, 1, 1, 1, 2, 3, 4}.
时间复杂:对于一般的二叉树为
O(n2)
。 如果使用平衡二叉查找树则时间复杂度为
O(nlogn)
。
Method5. Moore’s Voting Algorithm
该算法有两个步骤:
1. 获取数组中出现次数最多的元素。这个过程将会确保如果存在一个主元素(majority element), 就讲该元素返回。
2. 检查从上面步奏获取的元素是否是主元素。
(1). 找出候选元素 (finding a Candidate):
我们可以使用Moore’s Voting Algorithm来找出该元素,其时间复杂度为
O(n)
。该算法最基本的思想就是:如果e是一个主元素, 那么我们就可以抵消所有与e不相同的元素对。并且剩下的元素一定是e。
也可以如下理解:
每次都找出一对不同的元素,从数组中删掉,直到数组为空或只有一种元素。 不难证明,如果存在元素e出现频率超过半数,那么数组中最后剩下的就只有e。
Moore’s Voting Algorithm伪代码为:
findCandidate(a[], size)
1. Initialize index and count of majority element
maj_index = 0, count = 1
2. Loop for i = 1 to size – 1
(a)If a[maj_index] == a[i]
count++
(b)Else
count--;
(c)If count == 0
maj_index = i;
count = 1
3. Return a[maj_index]
Moore’s Voting Algorithm遍历了每一个元素,并且对元素 a[maj_index] 维持了一个计数器 count ,如果下一个元素与该元素相同那么 count 加1,如果不相同那么 count 减1。如果 count 等于0,那么把当前元素的索引 i 赋值给 maj_index 并且设置 count 为1。
Moore’s Voting Algorithm 算法选出了一个候选元素(candidate element)。那么接下来,我们就要去检查该元素是不是一个majority element。该过程很简单并且很容易在 O(n) 下做到。我们只需要去检查该元素出现的次数是否大于n/2.
例如:
A[] = 2, 2, 3, 5, 2, 2, 6
1). 初始化:
maj_index = 0, count = 1 –> candidate 2 ?
i = 1;
2 = a[maj_index] –> count = 2
i = 2;
3 != a[maj_index] –> count = 1
i = 3;
5 != a[maj_index] –> count = 0
因为,count= 0, 所以改变candidate为5 –> maj_index = 3, count = 1
i = 4;
2 != a[maj_index] —> count = 0 —> candidate 2, maj_index=4, count = 1
i = 5;
2 = a[maj_index] –> count = 2
i = 6;
6 != a[maj_index] –> count = 1
那么就有majority element = 2.
(2). 检查第一步所得到的元素是不是主元素majority element
printMajority (a[], size)
1. Find the candidate for majority
2. If candidate is majority. i.e., appears more than n/2 times.
Print the candidate
3. Else
Print "NONE"
注意:Moore’s Voting Algorithm 只有主元素一定存在时该算法才有效,否则该算法返回错误的结果。因此,第二步检查是有必要的!
Talk is cheap, show me the code !
// Moore’s Voting Algorithm
private static int findCandidate(ArrayList<Integer> nums) {
int maj_index = 0, count = 1;
for (int i = 1; i < nums.size(); ++i) {
if (nums.get(i).equals(nums.get(maj_index)))
count++;
else
count--;
if (count == 0) {
maj_index = i;
count = 1;
}
}
return nums.get(maj_index);
}
private static boolean isMajority(int [] nums, int cand) {
int count = 0;
for (int e : nums) {
if (e == cand)
++count;
}
return count > nums.length / 2 ;
}
Moore Voting Alogrithm 可以更加泛化的求解数组中至少出现 n/k 次的元素,详情请看Moore’s Voting Algorithm。