【算法基础】MEX的计算,MEX(minimal excluded) of a sequence

本文为翻译文章
原文链接 https://cp-algorithms.com/sequences/mex.html

MEX(minimal excluded) of a sequence

问题

给一个大小为 N N N的数组 A A A。从中找到最小的没有出现过的非负整数,这个数一般被叫做MEX(minimal excluded)
m e x ( { 0 , 1 , 3 , 4 , 5 } ) = 3 m e x ( { 0 , 1 , 2 , 3 , 4 } ) = 5 m e x ( { 1 , 2 , 3 , 4 , 5 } ) = 0 mex(\{0,1,3,4,5\}) = 3\\ mex(\{0,1,2,3,4\}) = 5 \\ mex(\{1,2,3,4,5\}) = 0 mex({0,1,3,4,5})=3mex({0,1,2,3,4})=5mex({1,2,3,4,5})=0
注意到,大小为 N N N的数组的MEX不可能比 N N N大。

最简单的求MEX的方法是把 A A A的所有元素都放到一个set里,这样就可以很快地检查一个数是否在这个集合里。只要我们按顺序检查从 0 0 0 N N N的所有数是否在集合里,如果当前数字不在集合里,就返回这个数。

代码实现

int mex(vector<int> const& A) {
    set<int> b(A.begin(), A.end());

    int result = 0;
    while (b.count(result))
        ++result;
    return result;
}

如果需要在 O ( N ) O(N) O(N)的时间内计算MEX,可以使用一个bool类型的vector来代替集合,这样我们需要的就是和 A A A一样大的一个数组。

int mex(vector<int> const& A) {
    static bool used[MAX_N+1] = { 0 };

    // mark the given numbers
    for (int x : A) {
        if (x <= MAX_N)
            used[x] = true;
    }

    // find the mex
    int result = 0;
    while (used[result])
        ++result;

    // clear the array again
    for (int x : A) {
        if (x <= MAX_N)
            used[x] = false;
    }

    return result;
}

这种方法很快,但是也仅仅是在当我们只需要计算一次MEX时才好用。如果你要一次又一次地计算MEX,这样就慢了。所以我们需要更好的方法。

带更新的MEX

问题:改变数组里的单个数,在每次更新后都计算数组里新的MEX。
需要使用更好的数据结构才能有效地解决这个问题。

一种方法是计算从 0 0 0 N N N的数出现的频率,然后用一个树形的数据结构比如线段树或treap来维护这些频率。每一个节点来维护一个区间里数的频率,并且存储当前区间中出现的不同的数的数量。每次更新需要 O ( l o g N ) O(logN) O(logN)的时间,通过二分在树上查找MEX需要 O ( l o g N ) O(logN) O(logN)的时间。

另一种方法是使用STL中的mapset来计算。用一个map来存储每个数出现的频率,用一个set来代表当前数组中没有出现的数。因为set是有序的,*set.begin()就是我们要求的MEX。需要用 O ( N l o g N ) O(NlogN) O(NlogN)的时间进行预处理,每次MEX计算的时间为 O ( 1 ) O(1) O(1),更新需要的时间为 O ( l o g N ) O(logN) O(logN)

class Mex {
private:
    map<int, int> frequency;
    set<int> missing_numbers;
    vector<int> A;

public:
    Mex(vector<int> const& A) : A(A) {
        for (int i = 0; i <= A.size(); i++)
            missing_numbers.insert(i);

        for (int x : A) {
            ++frequency[x];
            missing_numbers.erase(x);
        }
    }

    int mex() {
        return *missing_numbers.begin();
    }

    void update(int idx, int new_value) {
        if (--frequency[A[idx]] == 0)
            missing_numbers.insert(A[idx]);
        A[idx] = new_value;
        ++frequency[new_value];
        missing_numbers.erase(new_value);
    }
};
  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值