大家在学数组的阶段中,如果在Leetcode上做过关于数组的题,一定会碰到一道比较经典的数组题,就是在数组中找主要元素。题目具体如下:
这道题如果在不要求时间复杂度的情况下,我们可以很轻易的想到第一种解法,就是用两个指针,一个指向当前元素,另一个指针去遍历剩下的元素,看有没有与当前值相同的元素,记录下来个数。
int majorityElement(int* nums, int numsSize){
int cur = 0;
int next = 0;
int max = 0;
int num = 0;
int sum = 1;
while (cur < numsSize)
{
next = cur + 1;
sum = 1;
while (next < numsSize)
{
if (nums[cur] == nums[next])
sum++;
next++;
}
if (sum > max)
{
max = sum;
num = nums[cur];
}
cur++;
}
if (max <= (numsSize / 2))
return -1;
return num;
}
但是这样做的时间复杂度是O(N^2),在测试用例中的第44个会给5000个元素,运行时间过长。
那么我想到了第二种方法,就是将数组中的元素先进行排序,如果存在主要元素,那么这个数一定会在中间。但是这种方法的难点就是排序算法的时间复杂度,如果用冒泡排序的话,时间复杂度也是O(N^2),这样就和第一种方法没有什么区别。但是如果学过C++的同学可以运用C++中的sort排序函数,这样子会降低时间复杂度,但是仍然不是最优的算法。
第三种方法就是今天的主题:摩尔投票法
不知道是哪位大神创造出来的,非常简单易懂,而且能够满足复杂度为O(N)的要求。
大概思路就是主要元素的数量会超过数组长度的一半,因此设计一个计数器。当计数器为0的时候,用一个变量t等于当前数,然后计数器加一,往后遍历,如果遇到的数与当前数不同计数器减一,直到遍历完所有的元素,此时t所对应的元素就是主要元素。这个思想的核心就是主要元素的数量在遍历完所有元素的时候一定不是0,因为主要元素的数量是大于数组长度的一半,如果存在主要元素,计数器就一定不会是0。
这个思路中要注意一点,就是当循环一遍之后,计数器不是0的情况有两种:
一种是存在主要元素,计数器的数量一定不是0;
一种是没有主要元素,像下面这种情况:
循环出来,计数器的值也是1。
这就需要在一次遍历之后再遍历一遍数组,确认t所对应元素是主要元素。确认的方法是将计数器置0,然后把t所对应的元素与数组中的所有元素与t进行比较,如果计数器的长度小于等于数组长度的一半,t所对应的元素就不是主要元素,反之就是主要元素。
代码如下:
int majorityElement(int* nums, int numsSize){
int count = 0;
int t = 0;
int cur = 0;
while(cur < numsSize)
{
if(count == 0)
t = nums[cur];
if(t == nums[cur])
count++;
else
count--;
cur++;
}
//但是如果数组中没有主要元素的话,count也会是1,这就需要再对数组进行一次遍历,确认t所对应的元素是不是主要元素
count = 0;
for(int i = 0; i < numsSize; i++)
{
if(nums[i] == t)
count++;
}
if(count <= (numsSize / 2))
return -1;
return t;
}