青训营X豆包MarsCode 技术训练营第二课 | 豆包MarsCode AI 刷

48 阅读5分钟

今天我们继续借助MarsCode优化改进一道简单的算法题。

问题描述

小R从班级中抽取了一些同学,每位同学都会给出一个数字。已知在这些数字中,某个数字的出现次数超过了数字总数的一半。现在需要你帮助小R找到这个数字。


测试样例

样例1:

输入:array = [1, 3, 8, 2, 3, 1, 3, 3, 3]
输出:3

样例2:

输入:array = [5, 5, 5, 1, 2, 5, 5]
输出:5

样例3:

输入:array = [9, 9, 9, 9, 8, 9, 8, 8]
输出:9

第一步,我们使用最直观的写法。

#include <iostream>  
#include <vector>  
#include <unordered_map>

using namespace std;

// 函数:查找出现次数超过数组总数一半的数字  
int solution(const vector<int>& array) {  
    unordered_map<int, int> count_map; // 用于存储数字及其出现次数  
    int majority_count = array.size() / 2; // 计算超过一半的阈值
    // 遍历数组,统计每个数字的出现次数
    for (int num : array) {
        count_map[num]++;
        // 如果当前数字的出现次数超过了一半,返回该s数字
        if (count_map[num] > majority_count) {
            return num;
        }
    }
// 如果没有找到超过一半的元素,返回-1
return -1;
}
  1. 数据结构选择:使用unordered_map,它允许我们以O(1)的平均时间复杂度存储和检索数字的出现次数。
  2. 遍历数组:通过遍历输入数组,将每个数字及其出现次数记录在哈希表中。
  3. 检查条件:在统计每个数字的出现次数时,立即检查该数字的出现次数是否超过了数组长度的一半。如果超过,则返回该数字。
  4. 结果返回:如果遍历完成后没有找到任何数字出现次数超过一半,返回-1。

缺点是使用了O(n)级别的额外空间,十分不划算!

那怎么改进呢?

试想有这样一种情形,在一个遥远的村庄里,动物们决定选出新的村长。为了快速找到大家心目中的“最佳村长”,聪明的老乌龟提出使用一种投票法。投票开始时,老乌龟设定了一个候选者和一个计数器。

每当动物投票时,老乌龟根据以下规则进行:

  1. 如果计数器为0,就将候选者设为当前投票动物,并将计数器设为1。
  2. 如果当前投票动物与候选者相同,计数器加1。
  3. 如果不同,计数器减1。

投票结束后,老乌龟统计候选者的得票数,发现兔子获得了超过一半的支持。于是,兔子被选为新村长,承诺会用心管理村庄。这个投票方法被称为摩尔投票。

把它迁移到这道题上。

#include <iostream>
#include <vector>
using namespace std;

// 函数:查找出现次数超过数组长度一半的元素
int Solution(const vector<int>& array) {
    int candidate = 0; // 候选元素
    int count = 0;     // 计数器

    // 第一轮遍历:进行投票以确定候选元素
    for (int num : array) {
        if (count == 0) {
            candidate = num; // 更新候选者
            count = 1;       // 重置计数器
        } else if (num == candidate) {
            count++; // 候选者相同,计数加一
        } else {
            count--; // 候选者不同,计数减一
        }
    }

    // 第二轮遍历:验证候选元素的出现次数
    int actual_count = 0;
    for (int num : array) {
        if (num == candidate) {
            actual_count++; // 统计候选者的出现次数
        }
    }

    // 检查候选元素是否真的超过一半
    if (actual_count > array.size() / 2) {
        return candidate; // 返回有效的候选元素
    } else {
        return -1; // 返回-1表示没有找到有效的元素
    }
}
  1. 功能

    • 该代码实现了摩尔投票算法,用于寻找给定整数数组中出现次数超过一半的元素(多数元素)。
    • 通过两轮遍历,第一轮确定候选者,第二轮验证该候选者是否真的出现超过一半的次数。
  2. 时间复杂度

    • 时间复杂度为O(n),其中n是数组的长度。因为代码只需遍历数组两次。
  3. 空间复杂度

    • 空间复杂度为O(1),只使用了常量级别的额外空间(不依赖于输入数组大小)。

可以,空间复杂度极大的减小了。

但有一些缺点缺乏通用性。

-   该算法仅适用于寻找出现次数超过一半的元素,对于其他类型的问题(如查找频率最高的元素)则不适用。

不过这道题不受影响,还可以继续优化吗?

或还有没有其它好的解法

有的,这种思路是基于位操作的。具体来说,你可以将所有的数转换为二进制形式,然后逐位统计所有数在每一位上1的数量和0的数量。

方法描述:

  1. 统计每一位的1的数量

    • 对于数组中的每一个整数,都将其转换为二进制形式,统计每一位上1的数量。
  2. 比较每一位的数量

    • 对于每一位,比较统计到的1的数量和0的数量。如果某一位上1的数量超过了数组长度的一半,则该位的结果是1;否则结果是0。
  3. 构建结果

    • 最后,将得到的二进制位合并起来,得到最终的众数。
#include <iostream>#include <vector>using namespace std;
int find_majority_element(const vector<int>& array) { 
    int n = array.size(); 
    int result = 0; // 遍历32位整数的每一位 
    for (int bit = 0; bit < 32; bit++) { 
        int count = 0; // 统计当前位1的数量 
        for (int num : array) { 
            if (num & (1 << bit)) { 
                count++; 
            } 
        } // 比较1的数量与0的数量 
        if (count > n / 2) { 
        result |= (1 << bit); }
    } 
    return result;
}
  1. 空间复杂度:相比于使用哈希表或其他数据结构,该方法只使用了常量级的空间O(1)。
  2. 时间复杂度:该方法的时间复杂度为O(n),与摩尔投票法相同。
  3. 对负数处理:在处理负数时,二进制表示可能需要特殊处理(如补码表示)。

最后,我们提交结果,今天的任务完成了。