8. 找出整型数组中占比超过一半的数——“摩尔投票法”解决超半数问题

161 阅读3分钟

题目

www.marscode.cn/practice/r3…

image.png

方法一:字典统计法

思路

通过哈希表(字典)统计每个数字的出现次数,并实时更新出现次数最多的数字

  • 遍历数组,用字典记录每个数字的出现次数。

  • 每次更新字典时,检查当前数字的次数是否超过当前假定答案的次数。如果超过,则更新答案为当前数字。

  • 遍历完成后,返回最终答案(即出现次数最多的数字)。

代码

def solution(array):
    # Edit your code here
    ans = array[0]
    my_dict = {}
    for element in array:
        if element not in my_dict:
            my_dict[element] = 1
        else:
            my_dict[element] += 1

        if (my_dict[element] > my_dict[ans]):
            ans = element
    return ans


if __name__ == "__main__":
    # Add your test cases here

    print(solution([1, 3, 8, 2, 3, 1, 3, 3, 3]) == 3)

复杂度

  • 时间复杂度 O(n)O(n)
  • 空间复杂度 O(n)O(n),最坏的情况下数组中所有数字都不重复,需要 O(n)O(n) 的额外空间

方法二:摩尔投票法

注意到,题目中有一个条件是,某个数字的出现次数超过了数字总数的一半。然后这个条件在方法一中没有被使用,方法一更为通用,同时意味着有更好的解法。

摩尔投票法

摩尔投票法又称多数投票算法,是一种用来寻找一组元素中占多数元素的常数空间复杂度(O(1)O(1))、线性时间复杂度(O(n)O(n))算法。这一算法应用的问题原型是在集合中寻找可能存在的多数元素,这一元素在输入的序列重复出现并占到了序列元素的一半以上

思路

摩尔投票法的核心思路是抵消策略:如果一个数字的出现次数超过数组长度的一半,那么将它与其他数字一一抵消,最终剩下的数字必定是这个数字。

分为两阶段实现:

  • 第一阶段:通过遍历数组找到候选元素。

  • 第二阶段:验证候选元素是否真的超过数组长度的一半(只有在题目未明确保证存在满足条件的元素时需要验证)。

代码

def solution(array):
    # Edit your code here
    # 第一阶段:找出候选元素
    candidate = None
    count = 0

    for num in array:
        if count == 0:  # 如果计数为0,更新候选元素
            candidate = num
            count = 1
        elif num == candidate:  # 如果当前元素等于候选元素,计数+1
            count += 1
        else:  # 否则,计数-1
            count -= 1

    # 第二阶段:验证候选元素是否满足条件(此题可以直接return candidate)
    count = 0
    for num in array:
        if num == candidate:
            count += 1

    return candidate if count > len(array) // 2 else None


if __name__ == "__main__":
    # Add your test cases here

    print(solution([1, 3, 8, 2, 3, 1, 3, 3, 3]) == 3)

复杂度

  • 时间复杂度

    • 第一阶段遍历数组一次:O(n)O(n)
    • 第二阶段验证候选元素(必要时):O(n)O(n),当然这题直接return candidate即可。
    • 总体时间复杂度:O(n)O(n)
  • 空间复杂度

    • 只使用常数额外空间:O(1)O(1)

总结

  • 如果明确保证一定存在众数,且数据规模较大,使用摩尔投票法
  • 如果不确定众数是否存在,或者更追求实现简单,使用字典统计方法