解题思路详解
问题背景与分析
题目要求在一个整数数组中找到一个出现次数超过数组长度一半的数字(即多数元素)。这是一道经典的算法问题,广泛应用于投票计数、数据处理等场景。
本题的核心在于如何高效找到该多数元素。直观的做法是统计每个数字的出现次数,然后判断是否超过一半,但这种方法时间复杂度为 O(n2)O(n^2)(嵌套循环)或者 O(nlogn)O(n\log n)(使用排序)。为了提高效率,可以使用 摩尔投票法,一种基于贪心思想的线性时间复杂度算法。
方法选择:摩尔投票法
摩尔投票法利用多数元素的特性,通过遍历数组,仅用一个计数器完成多数元素的筛选。它的核心思想是“抵消”,即多数元素与非多数元素在计数器中相互抵消,最终剩下的候选数字即可能是多数元素。具体过程如下:
-
初始化一个候选数字
candidate和计数器count。 -
遍历数组时:
- 如果
count为 0,则将当前数字设为候选数字,并将计数器置为 1。 - 如果当前数字等于候选数字,则计数器加 1。
- 如果当前数字不等于候选数字,则计数器减 1。
- 如果
-
遍历结束后,
candidate为多数元素的候选数字。 -
验证候选数字:统计其在数组中的出现次数,判断是否满足多数元素的条件。
实现代码
以下是基于摩尔投票法的 Python 实现:
def solution(array):
# 初始化候选数字和计数器
candidate = None
count = 0
# 第一遍遍历,确定候选数字
for num in array:
if count == 0: # 如果计数器为0,更新候选数字
candidate = num
count = 1
elif candidate == num: # 如果当前数字等于候选数字,计数器加1
count += 1
else: # 如果当前数字不等于候选数字,计数器减1
count -= 1
# 第二遍验证,检查候选数字是否为多数元素
if array.count(candidate) > len(array) // 2:
return candidate
else:
return 0
# 测试用例
if __name__ == "__main__":
# 测试多数元素存在的情况
print(solution([1, 3, 8, 2, 3, 1, 3, 3, 3]) == 3) # 输出 True
print(solution([2, 2, 1, 1, 1, 2, 2]) == 2) # 输出 True
# 测试不存在多数元素的情况
print(solution([1, 2, 3, 4, 5]) == 0) # 输出 True
代码逻辑拆解
-
候选数字筛选:
- 遍历数组,利用计数器记录候选数字的“优势”。
- 候选数字的更新条件是当前计数器归零,即之前的候选数字在后续对比中已失去可能性。
- 计数器最终体现了当前候选数字的潜在“强度”。
-
验证候选数字:
- 因为摩尔投票法假设数组中必定存在多数元素,所以还需最后验证候选数字。
- 利用内置的
count方法统计候选数字的出现次数,判断是否超过数组长度的一半。
测试用例分析
-
测试用例一:
输入数组[1, 3, 8, 2, 3, 1, 3, 3, 3]。- 候选数字筛选过程:
1 → 3 → 8 → 3 → 3 → 3(最终候选数字为 3)。 - 验证阶段:数字
3出现次数为 5,超过数组长度的一半(9//2 = 4)。返回3。
- 候选数字筛选过程:
-
测试用例二:
输入数组[2, 2, 1, 1, 1, 2, 2]。- 候选数字筛选过程:
2 → 2 → 1 → 1 → 1 → 2 → 2(最终候选数字为 2)。 - 验证阶段:数字
2出现次数为 4,超过数组长度的一半(7//2 = 3)。返回2。
- 候选数字筛选过程:
-
测试用例三:
输入数组[1, 2, 3, 4, 5]。- 候选数字筛选过程:
1 → 2 → 3 → 4 → 5(最终候选数字为 5)。 - 验证阶段:数字
5出现次数为 1,未超过数组长度的一半(5//2 = 2)。返回0。
- 候选数字筛选过程:
算法复杂度分析
-
时间复杂度:
- 第一遍遍历数组确定候选数字,复杂度为 O(n)O(n)。
- 第二遍统计候选数字出现次数,复杂度为 O(n)O(n)。
- 总时间复杂度为 O(n)O(n)。
-
空间复杂度:
- 只使用了常量空间存储候选数字和计数器,复杂度为 O(1)O(1)。
思考与扩展
摩尔投票法的优雅之处在于其高效性,通过一次遍历解决了一个本质上涉及统计的问题。该算法适用于需要快速确定多数元素的场景,但需注意以下限制:
- 前提条件:数组中必须存在多数元素。如果没有,该算法可能返回错误结果(此处通过验证解决)。
- 应用场景扩展:摩尔投票法不仅适用于整数数组,还可以推广到字符串、分类数据等,只需改变比较规则。
通过这道题,能够体会到贪心算法的强大,同时也进一步加深对线性时间复杂度算法设计的理解。