解题思路
1.使用哈希表存储对应的元素和下标集合
2.使用二分查找:
a.最左查找(从左到右找到一个大于等于目标边界的位置)
b.最右查找(从右到左找到第一个小于等于目标边界的位置)
Note:
1.如果用其它语言如C++,可以使用 std::upper_bound(), 这个可以直接实现查找最左最右的位置
class RangeFreqQuery {
// Key: 目标元素
// Value: 对应元素在原数组中的下标集合,且该集合递增
HashMap<Integer, List<Integer>> map;
public RangeFreqQuery(int[] arr) {
map = new HashMap<>();
List<Integer> list = null;
for(int i = 0; i < arr.length; i++){
// 如果不存在则第一次创建
if(null == (list = map.get(arr[i]))){
list = new ArrayList<Integer>(1);
map.put(arr[i], list);
}
// 将对应的下标存储
// 这样存储,可以保证arr中相同的值,
// 在对应的list是递增的,方便后面的返回查找
list.add(i);
}
}
public int query(int left, int right, int value) {
List<Integer> list = null;
// 没有目标元素则直接返回0
if(null == (list = map.get(value)))
return 0;
int le = 0, ri = list.size() - 1;
// 当所给的返回超出实际存在的范围时直接返回
if(list.get(le) > right || list.get(ri) < left)
return 0;
/*
// 顺序查找,超时
for(int i = 0; i < list.size(); i++){
if(left <= list.get(i)){
le = i;
break;
}
}*/
// 使用二分查找left边界, 实现效果如上面备注处顺序查找
int tle = 0, tri = list.size() - 1;
while(tle < tri && tri >= 0){
int mid = tle + ((tri - tle) >> 1);
int ind = list.get(mid);
if(ind < left){
tle = mid + 1;
}else if(ind >= left){
tri = mid - 1;
}
}
// le: 指向list中从左到右第一个 “大于等于” left 的元素下标
le = tle;
/*
// 顺序查找,超时
for(int i = list.size() - 1; i >= 0; i--){
if(right >= list.get(i)){
ri = i;
break;
}
}*/
// 使用二分查找right边界, 实现效果如上面备注处顺序查找
// tle 已经在最左边,无需处理;
// 如果想让接下来的返回更小,可以在上面判断处,记录第一个出现 ind == left 的 mid
// tle = le;
tri = list.size() - 1;
while(tle < tri && tri >= 0){
int mid = tle + ((tri - tle) >> 1);
int ind = list.get(mid);
if(ind <= right){
tle = mid + 1;
}else if(ind > right){
tri = mid - 1;
}
}
// ri: 指向list中从右到左第一个 “小于等于” right 的元素下标
ri = tri;
// 修补左右边界
// TODO 个人感觉可以在上面直接确定,但目前不知道没有合适的方案
/*确保:
le: 指向list中从左到右第一个 “大于等于” left 的元素下标
ri: 指向list中从右到左第一个 “小于等于” right 的元素下标
*/
while(list.get(le) < left)
le++;
while(list.get(ri) > right)
ri--;
return ri - le + 1;
}
}