深入解析摩尔投票算法:豆包MarsCode 技术训练营实战 | 豆包MarsCode AI刷题

87 阅读3分钟

前言

在算法学习的道路上,实践是巩固知识、提升技能的最佳方式。参加了豆包MarsCode 技术训练营后,我有机会深入接触各种经典算法,并在实际问题中应用它们。今天,我想分享在训练营中遇到的一道经典算法题——寻找数组中出现次数超过一半的元素,以及背后的摩尔投票算法。

正文

问题描述

在训练营的一次编程练习中,我们遇到这样一个问题:

image.png

image.png

解题思路

初步想法

最直观的解法是使用哈希表统计每个元素出现的次数,然后找出出现次数超过 n/2 的元素。然而,这种方法需要 O(n) 的时间复杂度和 O(n) 的空间复杂度。在训练营的讨论中,我们被鼓励寻找更高效的算法。

摩尔投票算法

这时,我们可以询问豆包,介绍了摩尔投票算法(Boyer-Moore Voting Algorithm) 。它能够在 O(n) 的时间内、使用 O(1) 的空间,找到数组中出现次数超过一半的元素。

算法思想:

  • 候选人选择: 假设第一个元素为候选人,计数器初始化为1。

  • 计数器更新:

    • 如果遍历到的元素与候选人相同,计数器加1。
    • 如果不同,计数器减1。
  • 候选人更替:

    • 当计数器为0时,将当前元素设为新的候选人,计数器重置为1。
  • 结果验证: 遍历结束后,验证候选人是否确实为多数元素。

为什么可行?

在数组中,超过一半的元素数量一定比其他元素的总和还多。通过抵消的方式,其他元素最终会被多数元素“淹没”,因此最后剩下的候选人就是我们要找的元素。

代码实现

在训练营的实战环节,我们用 Java 实现了这个算法:

public class Main {
    public static int findMajorityElement(int[] array) {
        int candidate = array[0];
        int count = 1;

        // 第一遍遍历,寻找候选人
        for (int i = 1; i < array.length; i++) {
            if (array[i] == candidate) {
                count++;
            } else {
                count--;
                if (count == 0) {
                    candidate = array[i];
                    count = 1;
                }
            }
        }

        // 第二遍遍历,验证候选人是否为多数元素
        count = 0;
        for (int num : array) {
            if (num == candidate) {
                count++;
            }
        }
        if (count > array.length / 2) {
            return candidate;
        } else {
            throw new IllegalArgumentException("不存在出现次数超过一半的元素");
        }
    }

    public static void main(String[] args) {
        int[] array = {1, 3, 8, 2, 3, 1, 3, 3, 3};
        System.out.println("多数元素是:" + findMajorityElement(array));
    }
}

代码解析

  • 初始化候选人和计数器:

    int candidate = array[0];
    int count = 1;
    

    将数组的第一个元素设为候选人,计数器设为1。

  • 遍历寻找候选人:

    for (int i = 1; i < array.length; i++) {
        if (array[i] == candidate) {
            count++;
        } else {
            count--;
            if (count == 0) {
                candidate = array[i];
                count = 1;
            }
        }
    }
    
    • 遇到相同元素,计数器加1。
    • 遇到不同元素,计数器减1;若计数器为0,更新候选人。
  • 验证候选人是否为多数元素:

    count = 0;
    for (int num : array) {
        if (num == candidate) {
            count++;
        }
    }
    if (count > array.length / 2) {
        return candidate;
    } else {
        throw new IllegalArgumentException("不存在出现次数超过一半的元素");
    }
    

    重新统计候选人出现的次数,确认其是否超过一半。

算法分析

  • 时间复杂度: O(n) ,因为数组遍历了两遍。
  • 空间复杂度: O(1) ,只使用了常数级别的额外空间。

训练营心得

豆包MarsCode 技术训练营的学习中,我深刻体会到了算法优化的重要性。摩尔投票算法的巧妙之处在于利用了多数元素的性质,避免了额外的空间开销。

通过与其他学员的讨论,我也认识到在解决问题时,不能只停留在表面,而要深入思考问题的本质,寻找最优的解法。

思考

  • 扩展思考: 如果数组中不存在出现次数超过一半的元素,如何修改算法来处理这种情况?
  • 进阶挑战: 能否设计一个算法,找出数组中所有出现次数超过 n/3 的元素?

期待在训练营中与进一步交流和探讨。