【每日一题Day179】LC1157子数组中占绝大多数的元素 | 线段树

简介: 【每日一题Day179】LC1157子数组中占绝大多数的元素 | 线段树

*子数组中占绝大多数的元素【LC1157】

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

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

实现 MajorityChecker 类:

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

第一次做线段树 又被难到

  • 思路
    需要求解的问题有两个:找出 可能的 绝对众数和统计这个数出现的次数
  • 由于题目中2 * threshold > right - left + 1,因此可以得出结论:每次查询时不一定存在多数元素,但存在多数元素的话找到的一定是多数元素)

image.png

class Node {
    int l, r;
    int x, cnt;
}
class SegmentTree {
    private Node[] tr;
    private int[] nums;
    public SegmentTree(int[] nums) {
        int n = nums.length;
        this.nums = nums;
        tr = new Node[n << 2];
        for (int i = 0; i < tr.length; ++i) {
            tr[i] = new Node();
        }
        build(1, 1, n);
    }
    private void build(int u, int l, int r) {
        tr[u].l = l;
        tr[u].r = r;
        if (l == r) {// 若到达叶节点
            tr[u].x = nums[l - 1];// 存储值
            tr[u].cnt = 1;// 存储次数
            return;
        }
        int mid = (l + r) >> 1;
        // 左右递归
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
    public int[] query(int u, int l, int r) {
        if (tr[u].l >= l && tr[u].r <= r) {// 在区间内,直接返回 
            return new int[] {tr[u].x, tr[u].cnt};
        }
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (r <= mid) {
            return query(u << 1, l, r);// 答案在左子树
        }
        if (l > mid) {
            return query(u << 1 | 1, l, r);// 答案在右子树
        }
        // 答案与左右子树相关
        var left = query(u << 1, l, r);// 左子树的多数元素和次数
        var right = query(u << 1 | 1, l, r);// 右子树的多数元素和次数
        if (left[0] == right[0]) {// 值相等次数相加
            left[1] += right[1];
            return left;
        } else if (left[1] >= right[1]) {// 值不相等,返回次数较大值,次数为较大值次数-较小值次数 (因为如果相等那么表示,一定不存在多数元素)【不减直接返回也可以通过】
            // left[1] -= right[1];
            return left;
        } else {
            return right;
            // right[1] -= left[1];
            // left = right;
        }
    }
  // 更新节点信息 
    private void pushup(int u) {
        // 将节点信息更新为左右节点的次数最大值
        if (tr[u << 1].x == tr[u << 1 | 1].x) {// 相等 那么次数相加
            tr[u].x = tr[u << 1].x;
            tr[u].cnt = tr[u << 1].cnt + tr[u << 1 | 1].cnt;
        } else if (tr[u << 1].cnt >= tr[u << 1 | 1].cnt) {// 不相等 那么次数相减
            tr[u].x = tr[u << 1].x;
            tr[u].cnt = tr[u << 1].cnt - tr[u << 1 | 1].cnt;
        } else {
            tr[u].x = tr[u << 1 | 1].x;
            tr[u].cnt = tr[u << 1 | 1].cnt - tr[u << 1].cnt;
        }
    }
}
class MajorityChecker {
    private SegmentTree tree;
    private Map<Integer, List<Integer>> d = new HashMap<>();
    public MajorityChecker(int[] arr) {
        tree = new SegmentTree(arr);
        for (int i = 0; i < arr.length; ++i) {// 记录每个数出现的下标
            d.computeIfAbsent(arr[i], k -> new ArrayList<>()).add(i);
        }
    }
    public int query(int left, int right, int threshold) {
        int x = tree.query(1, left + 1, right + 1)[0];// 候选元素
        // 二分查找
        int l = search(d.get(x), left);// 原数组中下标小于left的x的个数
        int r = search(d.get(x), right + 1);// 原数组中下标小于right + 1的x的个数
        // 相减即为区间[l,r]中x的个数
        return r - l >= threshold ? x : -1;
    }
    private int search(List<Integer> arr, int x) {
        // 左闭右开
        int left = 0, right = arr.size();
        while (left < right) {
            int mid = (left + right) >> 1;
            if (arr.get(mid) >= x) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}

image.png


目录
相关文章
|
7月前
【每日一题Day155】LC1630等差子数组 | 枚举+排序
【每日一题Day155】LC1630等差子数组 | 枚举+排序
44 0
|
7月前
|
索引
【每日一题Day131】LC1144递减元素使数组呈锯齿状 | 贪心+枚举
【每日一题Day131】LC1144递减元素使数组呈锯齿状 | 贪心+枚举
49 0
|
7月前
【每日一题Day118】LC1124表现良好的最长时间段 | 前缀和+单调栈/哈希表
【每日一题Day118】LC1124表现良好的最长时间段 | 前缀和+单调栈/哈希表
57 0
|
算法
【代码随想录】LC 209. 长度最小的子数组
利用两层循环,第一层循环枚举子数组的起点位置,第二层循环枚举子数组的终点位置,第二层循环中可以同时来统计当前子数组的和,如果符合题目条件则更新length,否则继续循环,直至两层循环结束,返回题目要求的值,算法结束。
61 0
|
6月前
|
算法 测试技术 程序员
力扣经典150题第三十题:长度最小的子数组
力扣经典150题第三十题:长度最小的子数组
31 1
|
7月前
|
vr&ar
【每日一题Day166】LC1053交换一次的先前排列 | 贪心
【每日一题Day166】LC1053交换一次的先前排列 | 贪心
73 1
|
7月前
|
存储
【每日一题Day173】LC1019链表中的下一个更大节点 |单调栈
【每日一题Day173】LC1019链表中的下一个更大节点 |单调栈
34 0
|
7月前
【每日一题Day220】LC1439有序矩阵中的第 k 个最小数组和 | 堆
【每日一题Day220】LC1439有序矩阵中的第 k 个最小数组和 | 堆
75 0
|
算法 C++ Python
每日算法系列【LeetCode 658】找到 K 个最接近的元素
每日算法系列【LeetCode 658】找到 K 个最接近的元素
|
人工智能 算法 vr&ar
每日算法系列【LeetCode 1004】最大连续1的个数 III
每日算法系列【LeetCode 1004】最大连续1的个数 III