每日一题-1157. 子数组中占绝大多数的元素

56 阅读2分钟

1157. 子数组中占绝大多数的元素

难度:困难

题目:

设计一个数据结构,有效地找到给定子数组的 多数元素 。

子数组的 多数元素 是在子数组中出现 threshold 次数或次数以上的元素。

实现 MajorityChecker 类:

  • MajorityChecker(int[] arr) 会用给定的数组 arr 对 MajorityChecker 初始化。
  • int query(int left, int right, int threshold) 返回子数组中的元素  arr[left...right] 至少出现 threshold 次数,如果不存在这样的元素则返回 -1

 

示例 1:

输入:
["MajorityChecker", "query", "query", "query"]
[[[1, 1, 2, 2, 1, 1]], [0, 5, 4], [0, 3, 3], [2, 3, 2]]
输出:
[null, 1, -1, 2]

解释:
MajorityChecker majorityChecker = new MajorityChecker([1,1,2,2,1,1]);
majorityChecker.query(0,5,4); // 返回 1
majorityChecker.query(0,3,3); // 返回 -1
majorityChecker.query(2,3,2); // 返回 2

 

提示:

  • 1 <= arr.length <= 2 * 104
  • 1 <= arr[i] <= 2 * 104
  • 0 <= left <= right < arr.length
  • threshold <= right - left + 1
  • 2 * threshold > right - left + 1
  • 调用 query 的次数最多为 104

个人思路

思路与算法

解法一、暴力

问题分为两个部分:

找出 可能的 绝对众数(不一定存在绝对众数,但存在绝对众数的话找到的一定是绝对众数)和统计这个数出现的次数。

找可能的绝对众数方法很简单,暴力寻找即可(类似于打擂台,具体实现可以看代码,正确性证明略去)。

统计次数也使用暴力统计。

设数组长度为 n,询问次数为 q,则总时间复杂度为 O(nq),无法通过。

代码


class MajorityChecker {
    int n,a[20005];
public:
    MajorityChecker(vector<int>& arr) {
        n=arr.size();
        for(int i=0;i<n;i++)a[i]=arr[i];
    }
    
    int query(int left, int right, int threshold) {
        int i,j,k;
        j=k=0;
        for(i=left;i<=right;i++)if(a[i]==j)k++;
        else if(k)k--;
        else
        {
            j=a[i];
            k=1;
        }
        for(i=left,k=0;i<=right;i++)if(a[i]==j)k++;
        if(k<threshold)j=-1;
        return j;
    }
};

解法二:线段树

注意到暴力算法维护的信息满足可加性(即可以快速合并两个子段的信息得到完整段的信息),因此可以使用线段树维护。具体实现可以参见代码。因此寻找可能的绝对众数的时间复杂度为 O(log⁡n)。

数值范围是 [1,20000],因此使用 20000 个 vector 存储每一个数出现的位置,使用 lower_bound 和 upper_bound 即可在 O(log⁡n) 的时间复杂度内找出一个数在一个区间内的出现次数。

因此,单次询问的时间复杂度为 O(logn),预处理时间复杂度为 O(n),总时间复杂度为 O(n+qlogn)。(貌似由于常数 + 数据原因,分块比线段树还快)

代码

class MajorityChecker {
    struct node
    {
        int x,y;
        node operator+(const node& b)const
        {
            node t;
            if(x==b.x)
            {
                t.x=x;
                t.y=y+b.y;
            }
            else if(y<b.y)
            {
                t.x=b.x;
                t.y=b.y-y;
            }
            else
            {
                t.x=x;
                t.y=y-b.y;
            }
            return t;
        }
    }t[65536];
    int n,a[20005];
    vector<int> s[20005];
    void build(int R,int l,int r)
    {
        if(l==r)
        {
            t[R].x=a[l];
            t[R].y=1;
            return;
        }
        int mid=l+r>>1;
        build(R<<1,l,mid);
        build(R<<1|1,mid+1,r);
        t[R]=t[R<<1]+t[R<<1|1];
    }
    node ask(int R,int l,int r,int l1,int r1)
    {
        if(l1==l&&r==r1)return t[R];
        int mid=l+r>>1;
        if(r1<=mid)return ask(R<<1,l,mid,l1,r1);
        if(l1>mid)return ask(R<<1|1,mid+1,r,l1,r1);
        return ask(R<<1,l,mid,l1,mid)+ask(R<<1|1,mid+1,r,mid+1,r1);
    }
public:
    MajorityChecker(vector<int>& arr) {
        n=arr.size();
        int i;
        for(i=0;i<n;i++)s[a[i]=arr[i]].push_back(i);
        build(1,0,n-1);
    }
    
    int query(int left, int right, int threshold) {
        int ans=ask(1,0,n-1,left,right).x;
        if(upper_bound(s[ans].begin(),s[ans].end(),right)-lower_bound(s[ans].begin(),s[ans].end(),left)<threshold)ans=-1;
        return ans;
    }
};

image.png

每天记录一下做题思路。