问题描述
小R从班级中抽取了一些同学,每位同学都给出了一个数字。已知在这些数字中,有一个数字的出现次数超过了总数的一半。我们的任务是找出这个数字。
关键点分析
- 存在性证明:根据题目条件,我们知道这样的数字是一定存在的,因为有一个数字的出现次数超过了总数的一半。
- 排序的局限性:虽然排序可以帮助我们直观地看到哪些数字是连续的,但在这个问题中,排序并不是最高效的解决方案,因为它的时间复杂度是O(n log n)。
- 摩尔投票算法:这是一个更高效的算法,其时间复杂度为O(n),并且只需要O(1)的额外空间。该算法通过遍历数组,同时维护一个候选数字和计数器来找到出现次数超过一半的数字。
初始代码
def solution(array):
# 排序数组
array.sort()
# 初始化变量
count = 1 # 当前数字的出现次数
majority_count = len(array) // 2 # 超过一半的数字应该出现的次数
# 遍历排序后的数组
for i in range(1, len(array)):
if array[i] == array[i - 1]:
count += 1 # 如果当前数字与前一个数字相同,则增加计数器
else:
count = 1 # 如果不同,则重置计数器
# 检查计数器是否超过了数组长度的一半
if count > majority_count:
return array[i] # 返回当前数字(因为已经排序,所以返回i或i-1都可以,但i更直观)
return 0
if __name__ == "__main__":
print(solution([1, 3, 8, 2, 3, 1, 3, 3, 3]) == 3) # 输出: True
初始代码使用了排序的方法,虽然可以解决问题,但效率不是最优的,以下是对其的优化:
摩尔投票算法介绍
摩尔投票算法是一种在数组中寻找出现次数超过一半元素的线性时间算法。其核心思想在于,如果一个数字的出现次数超过数组长度的一半,那么它在遍历数组的过程中,一定会出现比其他所有数字都更多的次数。利用这一特性,我们可以一边遍历数组,一边记录当前的“候选数字”及其“出现次数”。
算法步骤
-
初始化:
- 设定一个
candidate变量,用于记录当前的候选数字。 - 设定一个
count变量,用于记录候选数字的出现次数。
- 设定一个
-
遍历数组:
-
对于数组中的每个数字
num:- 如果
count为0,说明之前的候选数字已经被抵消完,因此将num设为新的候选数字,并将count设为1。 - 如果
num与candidate相同,则count加1。 - 如果
num与candidate不同,则count减1。
- 如果
-
-
验证候选数字:
- 遍历完数组后,我们得到了一个候选数字。为了确认它是否真的是出现次数超过一半的数字,我们需要再次遍历数组进行验证。
- 在第二次遍历中,我们统计候选数字的出现次数,并检查是否超过数组长度的一半。
问题中的应用
在问题中,我们需要找到一个在数组中出现次数超过一半的数字。这正好符合摩尔投票算法的应用场景。
示例解析
以数组[1, 3, 8, 2, 3, 1, 3, 3, 3]为例:
-
初始化
candidate为None,count为0。 -
遍历数组:
- 遇到1,
count变为1,candidate变为1。 - 遇到3,
count加1,变为2。 - 遇到8,
count减1,变为1。 - 遇到2,
count减1,变为0。此时,candidate更新为2(但紧接着被3替换)。 - 遇到3,
count变为1,candidate更新为3(因为count为0)。 - 后续遇到的3都会使
count增加。
- 遇到1,
-
遍历结束后,
candidate为3,且其出现次数超过了数组长度的一半。 -
进行验证(虽然理论上不需要,但为了代码健壮性):再次遍历数组,确认3的出现次数确实超过了数组长度的一半。
优化后的代码(使用摩尔投票算法)
def solution(array):
candidate = None
count = 0
# 第一阶段:找出候选数字
for num in array:
if count == 0:
candidate = num
count = 1
elif num == candidate:
count += 1
else:
count -= 1
# 第二阶段:验证候选数字是否真的是多数数字
count = 0
for num in array:
if num == candidate:
count += 1
# 检查候选数字的出现次数是否超过数组长度的一半
if count > len(array) // 2:
return candidate
else:
return None
if __name__ == "__main__":
print(solution([1, 3, 8, 2, 3, 1, 3, 3, 3]) == 3) # 输出: True
注意:在优化后的代码中,我们使用了摩尔投票算法来找出候选数字,并在第二阶段验证了该数字是否真的是出现次数超过一半的数字。虽然题目保证了条件成立,但我们在实际编程中应该考虑代码的健壮性,因此保留了验证步骤。如果验证失败,可以返回一个错误提示或抛出异常。在这个特定问题中,由于题目已经保证了条件成立,所以验证步骤理论上不会失败。