问题描述
在一个数组中,如果存在某个数字的出现次数超过数组长度的一半,我们需要找到这个数字。如果不存在这样的数字,返回 None。
这是一个经典的问题,考察我们如何在遍历数组的过程中有效统计并分析每个数字的出现频率。
解题思路
方法分析:
- 字典统计频率:
使用一个字典来存储每个数字的出现次数。在遍历数组时,更新字典中的统计数据。 - 判断条件:
在统计完成后,我们遍历字典中的数据,判断是否存在一个数字的频率大于数组长度的一半。如果存在,返回该数字。
实现步骤:
- 创建一个字典
count_dict,用来记录每个数字的出现次数。 - 遍历数组,统计每个数字的频率,并更新到字典中。
- 计算数组长度的一半
half_length,判断字典中是否有数字的出现次数大于这个值。 - 如果找到符合条件的数字,返回该数字;否则,返回
None。
代码实现
def solution(array):
# 创建一个字典来记录每个数字的出现次数
count_dict = {}
# 遍历数组,统计每个数字的出现次数
for num in array:
if num in count_dict:
count_dict[num] += 1
else:
count_dict[num] = 1
# 计算数组长度的一半
half_length = len(array) // 2
# 遍历字典,找出出现次数超过一半的数字
for num, count in count_dict.items():
if count > half_length:
return num
# 如果没有找到符合条件的数字,返回None(理论上不会发生)
return None
if __name__ == "__main__":
# Add your test cases here
print(solution([1, 3, 8, 2, 3, 1, 3, 3, 3]) == 3)
代码解析
- 统计频率:
使用字典 count_dict 保存每个数字的出现次数,num 是数组中的数字。
如果数字 num 已在字典中,则将其值加 1;否则,初始化为 1。
- 判断条件:
计算数组长度的一半 half_length = len(array) // 2。
遍历 count_dict 中的键值对 (num, count),判断是否有 count > half_length 的数字。
- 边界处理:
如果字典遍历完成后仍未找到符合条件的数字,返回
None。
复杂度分析
- 时间复杂度:
遍历数组的复杂度为 (O(n)),统计频率字典的复杂度也是 (O(n))。因此总体时间复杂度为 (O(n))。
- 空间复杂度:
额外使用了一个字典存储数组中数字的频率。字典的空间复杂度取决于数组中不同数字的数量,最坏情况下为 (O(n))。
为了对于程序进行进一步优化,可以引入一个摩尔投票法,找在数组中出现次数超过一半的元素。该算法的优势在于只需 一次遍历 和 常数级的空间复杂度,而无需借助字典来统计频率。这样从一定程度上缩小了空间复杂度。
算法思路:
-
第一阶段:找到候选人
-
维护一个候选元素
candidate和计数器count。 -
遍历数组:
-
如果
count == 0,将当前数字设为候选人,并将count设置为 1。 -
如果当前数字等于候选人,将
count加 1,否则减 1。
- 遍历完成后,
candidate可能是目标元素。
-
-
第二阶段:验证候选人
- 遍历数组,统计
candidate的实际出现次数,判断是否大于数组长度的一半。
- 遍历数组,统计
def solution(array):
# 第一阶段:找到候选人
candidate = None
count = 0
for num in array:
if count == 0:
candidate = num
count += 1 if num == candidate else -1
# 第二阶段:验证候选人
if array.count(candidate) > len(array) // 2:
return candidate
return None
if __name__ == "__main__":
print(solution([1, 3, 8, 2, 3, 1, 3, 3, 3]) == 3)
print(solution([1, 2, 3, 4]) == None)
复杂度分析
- 时间复杂度: (O(n)),两次遍历数组。
- 空间复杂度: (O(1)),不需要额外字典存储频率统计数据。