1157 Online Majority Element In Subarray 子数组中占绝大多数的元素
描述
实现一个 MajorityChecker
的类,它应该具有下述几个 API
:
MajorityChecker(int[] arr)
会用给定的数组arr
来构造一个MajorityChecker
的实例。int query(int left, int right, int threshold)
有这么几个参数:0 <= left <= right < arr.length
表示数组arr
的子数组的长度。2 * threshold > right - left + 1
,也就是说阈值threshold
始终比子序列长度的一半还要大。
每次查询query(...)
会返回在arr[left]
,arr[left+1]
, ...,arr[right]
中至少出现阈值次数threshold
的元素,如果不存在这样的元素,就返回-1
。
示例:
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 <= 20000
1 <= arr[i] <= 20000
对于每次查询,0 <= left <= right < len(arr)
对于每次查询,2 * threshold > right - left + 1
查询次数最多为 10000
思路
- 首先读题:
- 查询
query
需要拥有一个出现次数超过查询长度一半
的元素众数
(出现次数超过总数一半的元素)思想: 拥有众数的数组, 数组内元素两两对消
(元素不相等)或两两相融
(元素相等), 剩下的必定是众数 - 查询次数上限
10000
, 需要保证查询速率 - 阈值次数
threshold
, 在查询范围内, 众数出现次数获取
可以使用二分查找
左右边界
- 查询
- 可以使用
动态规划
的思想:- 状态转移公式
l
查询左边界 r
查询右边界 val
查询区域众数 cnt
众数在查询区域出现次数
- 子问题合并
两子问题返回众数相等, 则出现次数相加, 不等则出现次数大减小
以此思想可以建立线段树
存储数据来加速查询速率
代码实现
使用线段树提前计算区域众数来加速查询
// 通过线段树来存储某些区域的众数和计数 以此来加速查询 class MajorityChecker { // idx 存储数组出现元素种类 以及该元素下标索引 // [1, 2, 2, 1, 1, 2] --> [[1 : 0, 3, 4], [2 : 1, 2, 5]] HashMap<Integer, ArrayList<Integer>> idx; // 线段树的根节点 SegTreeNode root; // key 所查找的区域众数 // count 所查找的区域众数出现次数 // 注意: 数组元素范围不包括0 int key=0, count=0; /** * 初始化 * 元素索引表idx 和 线段树root * * @param arr 被查询数组 * */ public MajorityChecker(int[] arr) { idx = new HashMap<>(); for (int i = 0; i < arr.length; i++) { if (!idx.containsKey(arr[i])) idx.put(arr[i], new ArrayList<Integer>()); idx.get(arr[i]).add(i); } root = buildTree(arr, 0, arr.length-1); } /** * 查询区域众数 是否超过阈值 * * @param left 查询区域左边界 * @param right 查询区域右边界 * @param threshold 用来判断众数的出现次数阈值 * @return key/-1 如果所查询众数key的查询区域出现次数超过阈值threshold则返回key, 否则返回-1 * */ public int query(int left, int right, int threshold) { // 初始化 所查询众数key 及辅助判断的计数count key = 0; count = 0; // 查询线段树 searchSegTree(root, left, right); // 如果查询区域没有众数 即key没被更改 // 或者 // 所查询出来的众数 在原数组中根本没有超出阈值的能力 if (key == 0 || idx.get(key).size() < threshold) return -1; // 上确界 排序数组中 第一个大于right的下标 int r = upper_bound(idx.get(key), right); // 下确界 排序数组中 第一个大于等于left的下标 int l = lower_bound(idx.get(key), left); count = r - l; return count >= threshold ? key : -1; } // 排序数组中 第一个大于tar的下标 int upper_bound(List<Integer> list, int tar) { int l = 0, r = list.size(); while (l < r) { int mid = l + (r-l)/2; if (list.get(mid) <= tar) l = mid+1; else r = mid; } return l; } // 排序数组中 第一个大于等于tar的下标 int lower_bound(List<Integer> list, int tar) { int l = 0, r = list.size()-1; while (l < r) { int mid = l + (r-l)/2; if (list.get(mid) < tar) l = mid+1; else r = mid; } return l; } /** * 构建线段树 * * @param arr 被构建数组 * @param l 构建节点的左值 表示查询区域左边界 * @param r 构建节点的右值 表示查询区域右边界 * @return 以构建完成的线段树节点 * */ private SegTreeNode buildTree(int[] arr, int l, int r) { if (l > r) return null; // 初始一个线段树节点 SegTreeNode root = new SegTreeNode(l, r); // 叶子节点 if (l == r) { // 众数就是当前值 计数为1 root.val = arr[l]; root.count = 1; return root; } int mid = (l+r)/2; // 构建左子节点 root.left = buildTree(arr, l, mid); // 构建右子节点 root.right = buildTree(arr, mid+1, r); // 整合父节点 makeRoot(root); return root; } /** * 整合一个父节点 * * @param root 被整合节点 * */ private void makeRoot(SegTreeNode root) { if (null == root) return; // 如果该节点有左子节点 该节点的值"先"等于左子节点 if (root.left != null) { root.val = root.left.val; root.count = root.left.count; } // 如果该节点还有右子节点 融合父节点和子节点 if (root.right != null) { if (root.val == root.right.val) root.count = root.count + root.right.count; else { if (root.count >= root.right.count) root.count = root.count - root.right.count; else { root.val = root.right.val; root.count = root.right.count - root.count; } } } } /** * 查询线段树 * * @param root 被查询节点 * @param l 需要查询的范围左边界 * @param r 需要查询的范围右边界 * */ private void searchSegTree(SegTreeNode root, int l, int r) { if (null==root | l > r) return; if (root.l > r | root.r < l) return; // 当查询边界覆盖节点边界 该节点就是查询区域 if (l <= root.l && root.r <= r) { if (key == root.val) count += root.count; else if (count <= root.count) { key = root.val; count = root.count - count; } else { count = count - root.count; } return; } int mid = (root.r + root.l)/2; // root.l <= l <= mid 左节点也可以是查询区域 if (l <= mid) { searchSegTree(root.left, l, r); } // mid+1 <= r <= root.r 右节点也可以是查询区域 if (r >= mid+1) { searchSegTree(root.right, l, r); } } /** * 线段树节点 类 * 用于存储区域边界/众数和计数 并且连接下一节点 * */ static class SegTreeNode { // l 所存储区域左边界 // r 所存储区域右边界 // val 所存储区域众数 // count 所存储区域众数计数(出现次数) int l, r, val, count; SegTreeNode left, right; SegTreeNode(int l, int r) { this.l = l; this.r = r; val = 0; count = 0; left = null; right = null; } } }