Leetcode1157

题意

题目链接
给了一个数组,有多组询问,每次问在区间 [ l , r ] [l, r] [l,r]内出现最多次数的数,次数是否 ≥ t h r e s h o l d \ge threshold threshold
数组长度 ≤ 2 ∗ 1 0 4 \le 2 * 10^4 2104
数组元素大小 1 ≤ x ≤ 1 0 4 1\le x \le 10^4 1x104
询问次数 ≤ 1 0 4 \le 10^4 104

思路

思路一

暴力做的话复杂度大概是 O ( N Q ) O(NQ) O(NQ)的, N N N为数组长度, Q Q Q为询问次数, 大概无法通过本题

思路二

分 块 分块
根据询问的长度,考虑不同的做法
如果区间的长度 < = <= <= s, 直接暴力计算, 时间复杂度为 O ( s ) O(s) O(s)
如果区间长度 > s, 那么众数出现的次数 > s 2 > \frac{s}{2} >2s(题目保证), 那么可能的答案的个数 ≤ 2 N s \leq \frac{2N}{s} s2N(一共N个数,出现次数 > s 2 > \frac{s}{2} >2s的数的个数 < n / s 2 = 2 N s < n / \frac{s}{2} = \frac{2N}{s} <n/2s=s2N
统计每个数在每个前缀出现的次数,枚举每个可能的数,可以在 O ( 1 ) O(1) O(1)内得到答案,总的时间复杂度为 O ( 2 N / s ) O(2N/s) O(2N/s)

两种情况的复杂度为 O ( s ) + O ( 2 n / 2 ) O(s) + O(2n/2) O(s)+O(2n/2), 取 s = 2 N s = \sqrt {2N} s=2N , 得到最优时间复杂度 O ( n ) O(\sqrt{n}) O(n ), 总复杂度为 ( ( N + Q N ) ) ((N+Q\sqrt{N})) ((N+QN ))

    int n, N, s;
    int[][] b = new int[205][20005];
    int[] d = new int[205];
    int[] a;
    Map mp = new HashMap<>();

    public MajorityChecker(int[] arr) {
        n = arr.length;
        N = 0;
        this.a = arr;
        for (int i = 0; i < n; i++) {
            mp.put(arr[i], mp.getOrDefault(arr[i], 0) + 1);
        }
        int s = (int) Math.sqrt(n * 2);
        mp.forEach((k, v) -> {
            if (v > s / 2) {
                b[++N][0] = 0;
                d[N] = k;
                for (int j = 0; j < n; j++) b[N][j + 1] = b[N][j] + (arr[j] == k ? 1 : 0);
            }
        });
    }

    public int query(int left, int right, int threshold) {
        int len = right - left + 1;
        if (len <= s) {
            int res = -1;
            int cnt = 0;
            //求众数
            for (int i = left; i <= right; i++) {
                if (a[i] == res) cnt++;
                else if (cnt > 0) cnt--;
                else {
                    res = a[i];
                    cnt = 1;
                }
            }
            cnt = 0;
            for (int i = left; i <= right; i++) {
                if (a[i] == res) cnt++;
            }
            if (cnt < threshold) res = -1;
            return res;
        } else {
            for (int i = 1; i <= N; i++) {
                if (b[i][right + 1] - b[i][left] >= threshold) return d[i];
            }
            return -1;
        }
    }

思路三

分 块 分块
和思路二差不多,考虑对 t h r e s h o l d threshold threshold的分情况求解,本质上和思路二相同
考虑一个阈值 s s s,当 t h r e s h o l d > s threshold>s threshold>s的时候,可能的结果只有 n s \frac{n}{s} sn个,这个时候使用前缀记录下这些数出现的次数,枚举每个数 O ( 1 ) O(1) O(1)求解
t h r e s h o l d < s thresholdthreshold<s的时候,长度是小于 2 ∗ s 2*s 2s的,暴力求解,复杂度和思路二也是差不多的

    int N = 20005;
    int len, s = 100, bsize;
    int[] c = new int[N];
    int[] a;
    List b;
    int[][] cnt;
    public MajorityChecker(int[] arr) {
        len = arr.length;
        this.a = arr;
        b = new ArrayList<>();
        for (int i = 0; i < len; i++) c[arr[i]]++;
        for (int i = 1; i <= 20000; i++) if (c[i] > s) b.add(i);
        bsize = b.size();
        cnt = new int[N][bsize];
        for (int i = 0; i < bsize; i++) {
            int num = b.get(i);
            for (int j = 0;j < len; j++) {
                cnt[j + 1][i] = cnt[j][i] + (a[j] == num ? 1 : 0);
            }
        }

    }

    public int query(int left, int right, int threshold) {
        if (threshold > s) {
            for (int i = 0; i < bsize; i++) {
                if (cnt[right + 1][i] - cnt[left][i] >= threshold) return b.get(i);
            }
        } else {
            int[] d = new int[20005];
            for (int i = left; i <= right; i++) {
                d[a[i]]++;
                if (d[a[i]] >= threshold) return a[i];
            }
        }
        return -1;
    }

思路四

线 段 树 线段树 线
每个节点维护区间内出现次数最大的那个数, 在用一个 v e c t o r vector vector维护每个数出现的下标,那么就可以通过二分快速计算出在一个区间内某个数出现的次数了,具体实现看https://zhuanlan.zhihu.com/p/77768691吧

参考资料

https://zhuanlan.zhihu.com/p/77768691
https://leetcode.com/contest/weekly-contest-149/ranking/1/
http://longrm.com/2019/08/16/2019-08-16-segment-tree/

你可能感兴趣的:(分块,leetcode)