给定一个大小为 N 的数组 arr[] 和一个包含 M 个查询的集合 Q[][] ,任务是在给定的数组上执行查询,这样可以有两种类型的查询:
- 类型 1: [i, x] – 将第i 个索引处的元素更新为 x。
- 类型 2: [k] – 查找数组中第k 个最小的元素。
例子:
Input: arr[] = {4, 3, 6, 2}, Q[][] = {{1, 2, 5}, {2, 3}, {1, 1, 7}, {2, 1}}
Output: 5 2
Explanation:
For the 1st query: arr[] = {4, 5, 6, 2}
For the 2nd query: 3rd smallest element would be 5.
For the 3rd query: arr[] = {7, 5, 6, 2}
For the 4th query: 1st smallest element would be 2.
Input: arr[] = {1, 0, 4, 2, 0}, Q[][] = {{1, 2, 1}, {2, 2}, {1, 4, 5}, {1, 3, 7}, {2, 1}, {2, 5}}
Output: 1 0 7
朴素的方法:这个问题的朴素的方法是在恒定时间内更新数组中的第i 个元素,并使用排序找到第 K个最小元素。
时间复杂度: O(M * (N * log(N))) 其中 M 是查询的数量,N 是数组的大小。
高效的方法:这个想法是使用类似于集合的基于策略的数据结构。
在这里,基于树的容器用于以排序树的形式存储数组,使得左边的所有节点都小于根,右边的所有节点都大于根。以下是数据结构的属性:
- 它通过维护节点不变性来索引,其中每个节点包含其子树中的节点计数。
- 每次我们插入一个新节点或删除一个节点时,我们可以通过冒泡到根来在 O(logN) 时间内保持不变量。
- 因此,左子树中节点的计数按排序顺序给出该节点的索引,因为左子树的每个节点的值都小于父节点。
因此,我们的想法是对每个查询遵循以下方法:
- 类型 1:对于这个查询,我们更新数组的第 i 个元素。因此,我们需要更新数组和数据结构中的元素。为了更新树容器中的值,在树中找到值 arr[i],将其从树中删除,并将更新后的值插入回树中。
- 类型 2:为了找到第 K个最小元素,在树上使用 find_by_order(K – 1) 因为数据是排序数据。这类似于排序数组上的二分查找操作。
下面是上述方法的实现:
// C++ implementation of the above approach
#include
#include
#include
using namespace std;
using namespace __gnu_pbds;
// Defining the policy based Data Structure
typedef tree,
null_type,
less >,
rb_tree_tag,
tree_order_statistics_node_update>
indexed_set;
// Elements in the array are not unique,
// so a pair is used to give uniqueness
// by incrementing cnt and assigning
// with array elements to insert in mySet
int cnt = 0;
// Variable to store the data in the
// policy based Data Structure
indexed_set mySet;
// Function to insert the elements
// of the array in mySet
void insert(int n, int arr[])
{
for (int i = 0; i < n; i++) {
mySet.insert({ arr[i], cnt });
cnt++;
}
}
// Function to update the value in
// the data structure
void update(int x, int y)
{
// Get the pointer of the element
// in mySet which has to be updated
auto it = mySet.lower_bound({ y, 0 });
// Delete from mySet
mySet.erase(it);
// Insert the updated value in mySet
mySet.insert({ x, cnt });
cnt++;
}
// Function to find the K-th smallest
// element in the set
int get(int k)
{
// Find the pointer to the kth smallest element
auto it = mySet.find_by_order(k - 1);
return (it->first);
}
// Function to perform the queries on the set
void operations(int arr[], int n,
vector > query, int m)
{
// To insert the element in mySet
insert(n, arr);
// Iterating through the queries
for (int i = 0; i < m; i++) {
// Checking if the query is of type 1
// or type 2
if (query[i][0] == 1) {
// The array is 0-indexed
int j = query[i][1] - 1;
int x = query[i][2];
// Update the element in mySet
update(x, arr[j]);
// Update the element in the array
arr[j] = x;
}
else {
int K = query[i][1];
// Print Kth smallest element
cout << get(K) << endl;
}
}
}
// Driver code
int main()
{
int n = 5, m = 6, arr[] = { 1, 0, 4, 2, 0 };
vector > query = { { 1, 2, 1 },
{ 2, 2 },
{ 1, 4, 5 },
{ 1, 3, 7 },
{ 2, 1 },
{ 2, 5 } };
operations(arr, n, query, m);
return 0;
}
1
0
7
时间复杂度:由于每个操作需要 O(Log(N)) 时间并且有 M 个查询,因此整体时间复杂度为O(M * Log(N)) 。
如果您想与行业专家一起参加直播课程,请参阅Geeks Classes Live