利用预处理与二分查找实现区间频率查询 — RangeFreqQuery 详解
题目描述
给定一个整数数组 arr,设计一个数据结构 RangeFreqQuery,支持如下操作:
- 构造函数
RangeFreqQuery(int[] arr):用数组arr初始化对象。 - 查询函数
query(int left, int right, int value):返回子数组arr[left...right]中元素value的出现次数(频率)。
子数组指的是数组中一段连续的元素。arr[left...right] 表示包含下标 left 和 right 的区间。
示例
RangeFreqQuery rangeFreqQuery = new RangeFreqQuery([12, 33, 4, 56, 22, 2, 34, 33, 22, 12, 34, 56]);
rangeFreqQuery.query(1, 2, 4); # 返回 1 ,因为子数组 [33, 4] 中 4 出现一次
rangeFreqQuery.query(0, 11, 33); # 返回 2 ,因为整个数组中 33 出现两次
解题分析
此问题的关键点在于,如何高效地支持大量区间查询。
朴素方法
- 对每次查询,遍历区间
arr[left...right]统计value出现次数。 - 时间复杂度为
O(n),如果查询次数很多(如10^5次),效率不够高。
高效方案思路
1. 预处理元素出现位置
- 对每个元素
val,提前记录它在数组中的所有出现索引(下标)。 - 用一个哈希表(字典)
pos,键是元素值,值是该元素所有出现位置的排序列表。
例如,数组 [12, 33, 4, 56, 22, 2, 34, 33, 22, 12, 34, 56]:
pos[33] = [1, 7]pos[12] = [0, 9]pos[22] = [4, 8]- …
2. 利用二分查找统计区间出现次数
- 查询时,已知目标区间
[left, right]和元素value。 - 在
pos[value]的索引列表中:- 找第一个位置 >=
left的索引l(用bisect_left) - 找第一个位置 >
right的索引r(用bisect_right)
- 找第一个位置 >=
r - l即为value在该区间出现的次数。
这种方法只需对每次查询做两次二分查找,时间复杂度约为 O(log n)。
代码实现(Python)
import bisect
from typing import List
class RangeFreqQuery:
def __init__(self, arr: List[int]):
self.pos = {}
for i, val in enumerate(arr):
if val not in self.pos:
self.pos[val] = []
self.pos[val].append(i)
def query(self, left: int, right: int, value: int) -> int:
if value not in self.pos:
return 0
positions = self.pos[value]
l = bisect.bisect_left(positions, left)
r = bisect.bisect_right(positions, right)
return r - l
复杂度分析
| 操作 | 时间复杂度 | 备注 |
|---|---|---|
| 构造函数 | O(n) | 遍历数组,记录元素位置 |
| 查询函数 | O(log n) | 二分查找左右边界 |
| 空间复杂度 | O(n) | 存储元素所有出现位置 |
其中,n 是数组长度。
方案比较
| 方法 | 时间复杂度(查询) | 备注 |
|---|---|---|
| 朴素遍历 | O(n) | 查询次数大时效率极低 |
| 线段树/树状数组+哈希 | O(log n)或更高 | 需要复杂的数据结构支持,编码复杂 |
| 预处理 + 二分查找 | O(log n) | 实现简单且高效,最优选择 |
示例说明
假设 arr = [12, 33, 4, 56, 22, 2, 34, 33, 22, 12, 34, 56],执行查询:
query(1, 2, 4)pos[4] = [2]- 在
[2]中找第一个 >=1 的位置是0,找第一个 >2 的位置是1,频率 = 1 - 0 = 1。
query(0, 11, 33)pos[33] = [1, 7]- 找第一个 >=0 的位置是0,找第一个 >11 的位置是2,频率 = 2 - 0 = 2。
总结
本题通过预处理每个元素出现的位置,结合二分查找实现了高效的区间频率查询。该方法简单易懂,且能够支持高达10^5次的查询,适合大规模数据场景。
884

被折叠的 条评论
为什么被折叠?



