📜  使用合并排序树的在线查询范围内的数组中不同数字的计数

📅  最后修改于: 2021-04-17 12:32:35             🧑  作者: Mango

给定大小为NQ个查询的数组arr [] ,其形式为[L,R],任务是在给定范围内找到此数组中不同值的数量。

例子:

天真的方法:一个简单的解决方案是,对于每个查询,将数组从L迭代到R并将元素插入到集合中。最后,集合的大小给出了从L到R的不同元素的数量。

时间复杂度: O(Q * N)

高效的方法:想法是使用合并排序树来解决此问题。

  1. 我们将元素的下一次出现存储在临时数组中。
  2. 然后,对于从L到R的每个查询,我们将在临时数组中找到在L到R范围内值大于R的元素数。

步骤1:取一个数组next_right,其中next_right [i]保存数组a中数字i的下一个右索引。将此数组初始化为N(数组的长度)。
第2步:根据next_right数组创建合并排序树并进行查询。计算从L到R的不同元素数量的查询等效于找到从L到R的元素数量,这些元素的数量大于R。

从给定数组构造合并排序树

  • 我们从一个段arr [0开始。 。 。 n-1]。
  • 每次将当前段划分为两个半段(如果尚未变成长度为1的段)时,然后在两个半段上调用相同的过程,对于每个这样的段,我们将排序后的数组存储在每个段中,就像合并排序一样。
  • 而且,该树将是完整的二叉树,因为我们总是在每个级别将分段分为两半。
  • 由于构造的树始终是具有n个叶的完整二叉树,因此将有N-1个内部节点。因此,节点总数将为2 * N – 1。

这是一个例子。假设1 5 2 6 9 4 7 1是一个数组。

|1 1 2 4 5 6 7 9|
|1 2 5 6|1 4 7 9|
|1 5|2 6|4 9|1 7|
|1|5|2|6|9|4|7|1|

next_right数组的构造

  • 我们存储每个元素的下一个正确出现的位置。
  • 如果元素最后一次出现,则我们存储’N’(数组的长度)
    例子:
    arr = [2, 3, 2, 3, 5, 6];
    next_right = [2, 3, 6, 6, 6, 6]
    

下面是上述方法的实现:

C++
// C++ implementation to find
// count of distinct elements 
// in a range L to R for Q queries
  
#include 
using namespace std;
  
// Function to merge the right
// and the left tree
void merge(vector tree[], 
                 int treeNode)
{
    int len1 = 
      tree[2 * treeNode].size();
    int len2 = 
      tree[2 * treeNode + 1].size();
    int index1 = 0, index2 = 0;
  
    // Fill this array in such a 
    // way such that values
    // remain sorted similar to mergesort
    while (index1 < len1 && index2 < len2) {
  
        // If the element on the left part
        // is greater than the right part
        if (tree[2 * treeNode][index1] > 
              tree[2 * treeNode + 1][index2]) {
  
            tree[treeNode].push_back(
                tree[2 * treeNode + 1][index2]
                );
            index2++;
        }
        else {
            tree[treeNode].push_back(
                tree[2 * treeNode][index1]
                );
            index1++;
        }
    }
  
    // Insert the leftover elements
    // from the left part
    while (index1 < len1) {
        tree[treeNode].push_back(
            tree[2 * treeNode][index1]
            );
        index1++;
    }
  
    // Insert the leftover elements
    // from the right part
    while (index2 < len2) {
        tree[treeNode].push_back(
            tree[2 * treeNode + 1][index2]
            );
        index2++;
    }
    return;
}
  
// Recursive function to build 
// segment tree by merging the 
// sorted segments in sorted way
void build(vector tree[], 
    int* arr, int start, int end, 
                  int treeNode)
{
    // Base case
    if (start == end) {
        tree[treeNode].push_back(
            arr[start]);
        return;
    }
    int mid = (start + end) / 2;
  
    // Building the left tree
    build(tree, arr, start, 
          mid, 2 * treeNode);
  
    // Building the right tree
    build(tree, arr, mid + 1, end, 
                 2 * treeNode + 1);
  
    // Merges the right tree
    // and left tree
    merge(tree, treeNode);
    return;
}
  
// Function similar to query() method
// as in segment tree
int query(vector tree[], 
     int treeNode, int start, int end, 
            int left, int right)
{
  
    // Current segment is out of the range
    if (start > right || end < left) {
        return 0;
    }
    // Current segment completely 
    // lies inside the range
    if (start >= left && end <= right) {
  
        // as the elements are in sorted order
        // so number of elements greater than R
        // can be find using binary 
        // search or upper_bound
        return tree[treeNode].end() - 
          upper_bound(tree[treeNode].begin(), 
            tree[treeNode].end(), right);
    }
  
    int mid = (start + end) / 2;
  
    // Query on the left tree
    int op1 = query(tree, 2 * treeNode, 
              start, mid, left, right);
    // Query on the Right tree
    int op2 = query(tree, 2 * treeNode + 1, 
            mid + 1, end, left, right);
    return op1 + op2;
}
  
// Driver Code
int main()
{
  
    int n = 5;
    int arr[] = { 1, 2, 1, 4, 2 };
  
    int next_right[n];
    // Initialising the tree
    vector tree[4 * n];
  
    unordered_map ump;
  
    // Construction of next_right 
    // array to store the
    // next index of occurence 
    // of elements
    for (int i = n - 1; i >= 0; i--) {
        if (ump[arr[i]] == 0) {
            next_right[i] = n;
            ump[arr[i]] = i;
        }
        else {
            next_right[i] = ump[arr[i]];
            ump[arr[i]] = i;
        }
    }
    // building the mergesort tree
    // by using next_right array
    build(tree, next_right, 0, n - 1, 1);
  
    int ans;
    // Queries one based indexing
    // Time complexity of each 
    // query is log(N)
  
    // first query
    int left1 = 0;
    int right1 = 2;
    ans = query(tree, 1, 0, n - 1, 
                  left1, right1);
    cout << ans << endl;
  
    // Second Query
    int left2 = 1;
    int right2 = 4;
    ans = query(tree, 1, 0, n - 1, 
                  left2, right2);
    cout << ans << endl;
}


输出:
2
3

时间复杂度: O(Q * log N)