【二分查找】2080. 区间内查询数字的频率

【二分查找】2080. 区间内查询数字的频率

前言

二分查找是一种高效的查找算法,其时间复杂度为 O(log n)。在许多情况下,我们需要在一个有序数组中找到某个目标值的搜索范围。本文将介绍一种基于二分查找的搜索范围查找算法,该算法能够快速找到目标值在数组中的起始和结束位置。
在另一篇博客里讲过二分法的模板:
《二分法的模板讲解》

作者: 迷茫的启明星

学习路线
C语言从0到1
C++初阶
数据结构从0到1

欢迎关注:点赞收藏✍️留言

码字不易,你的点赞收藏❤️关注对我真的很重要,有问题可在评论区提出,感谢阅读!!!

问题描述

给定一个整数数组 arr,设计一个数据结构 RangeFreqQuery,求出给定子数组内一个给定值的频率。子数组中一个值的频率指的是这个子数组中这个值的出现次数。

请你设计一个数据结构,它能求出给定子数组内一个给定值的 频率

子数组中一个值的 频率 指的是这个子数组中这个值的出现次数。

请你实现 RangeFreqQuery 类:

  • RangeFreqQuery(int[] arr) 用下标从 0 开始的整数数组 arr 构造一个类的实例。

  • int query(int left, int right, int value) 返回子数组 arr[left...right]value频率

一个 子数组 指的是数组中一段连续的元素。arr[left...right] 指的是 nums 中包含下标 leftright 在内 的中间一段连续元素。

原题链接:

https://leetcode.cn/problems/range-frequency-queries/description/

解题步骤

这是一个名为RangeFreqQuery的C++类,用于查询给定数组中指定范围[left, right]内指定数值value的出现次数。

解题步骤如下:

  1. 初始化:使用给定数组arr初始化内部哈希表occurence。遍历数组arr,将每个元素的值作为键,对应的下标数组作为值存储到哈希表中。

  2. 查询:

    a. 查找哈希表中是否存在指定数值value的键,如果不存在,返回0。

    b. 使用lower_bound和upper_bound函数分别查找大于等于left的最小下标和大于right的最小下标。这两个下标之间的差值就是value在给定范围内的出现次数。(也可以自己写二分函数)

    c. 返回查询结果。

代码实现

class RangeFreqQuery {
private: 
    // 数值为键,出现下标数组为值的哈希表
    unordered_map> occurence;
    
public:
    RangeFreqQuery(vector& arr) {
        // 顺序遍历数组初始化哈希表
        int n = arr.size();
        for (int i = 0; i < n; ++i){
            occurence[arr[i]].push_back(i);
        }
    }
    
    int query(int left, int right, int value) {
        // 查找对应的出现下标数组,不存在则为空
        const vector& pos = occurence[value];
        // 两次二分查找计算子数组内出现次数
        auto l = lower_bound(pos.begin(), pos.end(), left);
        auto r = upper_bound(pos.begin(), pos.end(), right);
        return r - l;
    }
};

相关知识点

哈希表(unordered_map)

哈希表是一种基于键值对的数据结构,可以快速查询、插入和删除数据。在本题中,哈希表的键为数值,值为该数值在数组中出现的下标数组。

二分查找(lower_bound 和 upper_bound)

二分查找是一种高效查找算法,可以快速定位到有序数组中大于或等于给定值的第一个元素。在本题中,使用二分查找来定位给定值在子数组中出现的起始和结束下标。

lower_bound函数:

在 C++ 中,lower_bound 是一个算法函数,用于在已排序的区间中查找第一个大于或等于给定值的元素。这个函数是 C++ STL(Standard Template Library,标准模板库)中的一个重要组成部分,位于 头文件中。

lower_bound 函数的原型如下:

template
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value);

其中,ForwardIt 是指向有序区间的迭代器,firstlast 分别表示区间的起始和终止迭代器,value 是要查找的目标值。函数返回一个指向大于或等于目标值的第一个元素的迭代器。如果未找到这样的元素,则返回 last

使用 lower_bound 函数的示例:

#include 
#include 
#include 
int main() {
  std::vector v = {1, 2, 3, 4, 5, 6, 7, 8, 9};
  int target = 5;
  auto it = std::lower_bound(v.begin(), v.end(), target);
  if (it != v.end()) {
      std::cout << "The first element greater than or equal to " << target << " is: " << *it << std::endl;
  } else {
      std::cout << "No elements greater than or equal to " << target << " found." << std::endl;
  }
  return 0;
}

这个示例中,我们首先创建一个包含整数 1 到 9 的向量。然后,我们使用 lower_bound 函数在向量中查找第一个大于或等于 5 的元素。最后,我们输出找到的元素。

upper_bound函数

C++中的`upper_bound`函数是STL(Standard Template Library,标准模板库)算法库中的一种二分查找算法。它用于在一个已排序的序列中查找一个值,并返回大于等于该值的第一个元素的迭代器。如果序列中不存在这样的元素,则返回序列末尾的迭代器。

`upper_bound`函数通常与排序算法(如`sort`)结合使用,用于对有序序列进行二分查找。函数签名如下:

```C++

template
ForwardIt upper_bound(ForwardIt first, ForwardIt last, const T& value);



    参数:

    - `first`:指向序列(通常是数组或容器)起始位置的迭代器。

    - `last`:指向序列末尾位置的迭代器。

    - `value`:要查找的值。

    返回值:

    - 返回一个迭代器,指向大于等于`value`的第一个元素的位置。如果序列中没有这样的元素,返回序列末尾的迭代器。

    示例:

    ```C++
#include 
#include 
#include  // 需要包含algorithm头文件以使用upper_bound函数
int main() {
    std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    std::sort(vec.begin(), vec.end()); // 对向量进行排序
    int target = 5;
    auto it = std::upper_bound(vec.begin(), vec.end(), target);
    if (it != vec.end()) {
        std::cout << "The first element greater than or equal to " << target << " is: " << *it << std::endl;
    } else {
        std::cout << "There is no element greater than or equal to " << target << " in the sequence." << std::endl;
    }
    return 0;
}

在这个例子中,我们首先对向量进行排序,然后使用`upper_bound`函数查找第一个大于等于`target`(即5)的元素。找到后,输出该元素的值;否则,输出相应的提示信息。

总结

本题通过哈希表和二分查找实现了在给定子数组范围内统计给定值出现频率的功能。通过哈希表记录每个数值及其对应下标,可以方便地获取给定值的出现位置。然后,使用二分查找在给定子数组范围内定位给定值的起始和结束下标,并计算给定值出现的频率。这种方法的时间复杂度为 O(n + m * log(n)),其中 n 为数组长度,m 为查询次数。

在实际应用中,可以根据具体需求对代码进行优化。例如,如果数组较大且查询次数较多,可以考虑使用线段树等更高效的数据结构。同时,也可以根据题目要求,对代码进行一定程度的简化或优化。

你可能感兴趣的:(#,leetcode刷题,算法,leetcode,c++,青少年编程,二分查找)