寻找主要元素
本文的pdf文件点此链接:link
“主要元素”是指一个元素在一个数组中出现的次数超过了这个数组长度的一半。
本文采用了四种不同的方法来解决这个问题,并根据不同大小的数组来验证不同算法的时间复杂度和空间复杂度。
一、问题描述
1.1主要元素
在一个给定的数组中,若一个元素的出现次数多余数组长度的二分之一,则称其为这个数组的主要元素。
因为一个数组有主要元素的条件为主要元素的个数必须严格大于数组的二分之一,所以一个数组只能有一个主要元素。
1.2需要解决的问题
给定一个数组,找出其中的主要元素并返回,若没有主要元素则返回-1。
二、方法分析
对于本题运用三种方法进行求解,下面分别使用特例来对四种方法进行具体分析。
给出下列示例数组:
[1,3,4,2,5,5,5,5,2,5,5]
2.1 暴力求解
思路:首先遍历整个数组,对每一个出现的元素进行计数,最后找出其中计数个数最多的元素,若其计数大于数组长度的二分之一,则有主要元素。
过程:
1、开始遍历,第一个元素为‘1’,新建一个数组,将‘1’放入其中;
2、第二个元素为‘3’,判断其与已经遍历过的所有元素是否相同,其与‘1’不同,则新建一个数组将‘3’放入其中;
3、第三个元素为‘4’,判断其与已经遍历过的所有元素是否相同,其与‘1’‘3’不同,则新建一个数组将‘4’放入其中;
4、第四个元素为‘2’,判断其与已经遍历过的所有元素是否相同,其与‘1’‘3’‘4’不同,则新建一个数组将‘2’放入其中;
5、第五个元素为‘5’,判断其与已经遍历过的所有元素是否相同,其与‘1’‘3’‘4’‘2’不同,则新建一个数组将‘5’放入其中;
6、第六个元素为‘5’,判断其与已经遍历过的所有元素是否相同,其与‘5’相同,则将‘5’放入已经建立的含有‘5’的数组中;
7、第七个元素为‘5’,判断其与已经遍历过的所有元素是否相同,其与‘5’相同,则将‘5’放入已经建立的含有‘5’的数组中;
8、第八个元素为‘5’,判断其与已经遍历过的所有元素是否相同,其与‘5’相同,则将‘5’放入已经建立的含有‘5’的数组中;
9、第九个元素为‘2’,判断其与已经遍历过的所有元素是否相同,其与‘2’相同,将其放入‘2’数组中;
10、第十个元素为‘5’,判断其与已经遍历过的所有元素是否相同,其与‘5’相同,则将‘5’放入已经建立的含有‘5’的数组中;
11、第十一个元素为‘5’,判断其与已经遍历过的所有元素是否相同,其与‘5’相同,则将‘5’放入已经建立的含有‘5’的数组中;
12、统计每个数组的长度找出其中最大的数组max(1,1,1,2,6)=6>5,则所给数组有主要元素,其主要元素为‘5’。
时间复杂度:O(n)
空间复杂度:O(n)
2.2 排序
如果存在主要元素,则对整个数组进行排序后中间元素的值必为主要元素。
对原数组进行排序:
1 2 2 3 4 5 5 5 5 5 5
其中间元素为‘5’,因为不确定改数组是否具有主要元素,因此需要判断一下‘5’是否为主要元素,统计‘5’出现的次数:6次,大于数组长度的一半,因此该数组有主要元素‘5’。
时间复杂度:O(nlogn)
空间复杂度:O(1)
2.3 摩尔投票法
投票法的基本原理是,维护一个众数major和一个频数count,如果出现不同的数count加1,如果出现相同的数,count-1。最终会发现,如果存在主要元素,那么最终count一定大于0,否则一定不存在主要元素。但仅大于0也不一定能判断确实存在主要元素,因为如果数组为[4,3,3,2,2,2],会发现count为2但是,2并不是主要元素,所以还要添加验证环节。
时间复杂度:O(n)
空间复杂度:O(1)
2.4 部分排序
受到了选择排序的思想的启发,我们可以考虑不将整个数组全部排序,而是只将其一半进行排序,由2.2我们得知,一个数组如果存在主要元素,则其一定为中间的元素值,那么我们考虑只从小到大或者从大至小将数组的一半元素进行排序,然后判断最后排好的那个元素是不是这个数组的主要元素即可。
1 2 2 3 4 5 5 5 5 5 5
1 2 2 3 4 5
如上,下面只许判断‘5’是否是该数组的主要元素即可。
查找:遍历整个数组,记录‘5’出现的次数,如果大于元素个数的一半,则‘5’是该数组的主要元素,否则无主要元素。
时间复杂度:O(n)
空间复杂度:O(n)
三、python实践
3.1
def majorityElement1(nums):
dicts={}
for i in nums:
dicts[i]=dicts.get(i,0)+1
mid=int(len(nums)/2);
#print(dicts)
for i,v in dicts.items():
if v>mid:
return i
return -1
3.2
def majorityElement2(nums):
res = -1
mid = int(len(nums) / 2)
nums.sort()
for i in range( 0 , len(nums) - mid):
if(nums[i] == nums[i + mid]):
res = nums[i]
break
return res;
3.3
def majorityElement3(nums):
if len(nums) == 0:
return -1
else:
count = 0
major = None
for i in range(len(nums)):
if count == 0:
major = nums[i]
count += 1
else:
if nums[i] != major:
count -= 1
else:
count += 1
if count > 0:
count_identify = 0
for i in nums:
if i == major:
count_identify += 1
if count_identify > int(len(nums) / 2):
return major
return -1
else:
return -1
3.4
def majorityElement4(nums):
main_element = number = 0
nums_copy = []
for i in range(len(nums)):
nums_copy.append(nums[i])
for i in range(int(len(nums_copy)/2)+1):
main_element = min(nums_copy)
nums_copy.remove(min(nums_copy))
for i in range(0, len(nums)):
if nums[i] == main_element:
number += 1
if number > len(nums)/2:
return main_element
else:
return -1
3.4 计时
import time
nums = [1, 3, 4, 2, 5, 5, 5, 5, 2, 5, 5]
#nums=[]
# for i in range(0,10000):
# num=random.randint(0,100)
# nums.append(num)
# print(nums)
print('方法1')
start1 = time.perf_counter()
print('Start time =', start1)
a = majorityElement1(nums)
end1 = time.perf_counter()
print('end time =', end1)
print('Runing time:{}Seconds'.format(end1-start1))
print('Result =', a)
print('\n')
print('方法2')
start2 = time.perf_counter()
print('start time =', start2)
b = majorityElement2(nums)
end2 = time.perf_counter()
print('end time =', end2)
print('Runing time:{}Seconds'.format(end2-start2))
print('Result =', b)
print('\n')
print('方法3')
start3 = time.perf_counter()
print('start time =', start3)
c = majorityElement3(nums)
end3 = time.perf_counter()
print('end time =', end3)
print('Runing time:{}Seconds'.format(end3-start3))
print('Result =', c)
print('\n')
print('方法4')
start4 = time.perf_counter()
print('start time =', start3)
d = majorityElement4(nums)
end4 = time.perf_counter()
print('end time =', end4)
print('Runing time:{}Seconds'.format(end4-start4))
print('Result =', d)
四、结果分析
4.1 数组长度为11
4.2 数组长度为100
4.3 数组长度为1000
4.4 数组长度为10000
4.5 数组长度为100000
五、思考
主要针对解决过程中出现的问题进行分析,在使用一个算法求解问题时,分析其时间复杂度和空间复杂度十分必要,如果一个算法的时间复杂度过高,则其可能在我们预期的时间内无法求解出答案,而空间复杂度过高则可能在运行过程中会出现内存不足等问题。
由于本问题主要针对的是数组,其内存占用比较少,所以我们不考虑其空间复杂度,主要研究其时间复杂度。
针对方法二和方法三,方法二的时间复杂度就是排序算法的时间复杂度为O(nlogn),而问题三的时间复杂度为O(n),所以我们猜想方法二的求解时间应该比方法三的求解时间长,但是根据不同维度的问题的求解我们发现,方法二的消耗时间均低于方法三,这与我们分析的结果不相同,所以对这个问题进行深入分析。
根据计算时间的函数,得到方法2和方法3在不同维度下的运行时间,我们发现,方法2的消耗时间总是少于方法三,这显然与先前的分析不同。
我们发现,在方法二中进行的比较操作很少,而在方法三中进行的比较次数却很多,所以导致其运行时间加长,但是我们在分许算法时间复杂度时往往会忽略循环中的语句操作,而更多的关注循环本身,所以导致仅依赖时间复杂度来断定具体的运行时间是不准确的,对于大规模问题的求解,我们应该将所有可能的时间均考虑在内,这样才能尽可能的得到可靠的时间分析。