📌  相关文章
📜  从具有更新的字符串查找范围[L,R]中的Kth个最大字符的查询(1)

📅  最后修改于: 2023-12-03 15:21:55.718000             🧑  作者: Mango

从具有更新的字符串查找范围[L,R]中的Kth个最大字符的查询

这个问题可以使用堆和线段树结合的方法来解决。我们可以使用堆来维护区间[L, R]中出现次数最多的K个字符,然后使用线段树来维护这些字符的出现次数。

算法

定义一个结构体Node来表示线段树的节点:

struct Node {
    int l, r;
    vector<pair<char, int>> freq;
    // freq用来维护[L, R]中出现次数最多的K个字符
    Node *left, *right;
    // left和right分别表示左子树和右子树
};

定义一个函数cmp,用于堆的比较:

bool cmp(const pair<char, int>& a, const pair<char, int>& b) {
    return a.second < b.second;
}

然后定义一个函数merge,用于合并两个freq数组:

void merge(vector<pair<char, int>>& a, const vector<pair<char, int>>& b, int k) {
    for (auto x : b) {
        if (a.size() < k) {
            a.push_back(x);
            push_heap(a.begin(), a.end(), cmp);
        } else if (cmp(x, a.front())) {
            pop_heap(a.begin(), a.end(), cmp);
            a.back() = x;
            push_heap(a.begin(), a.end(), cmp);
        }
    }
}

该函数将b中的元素合并到a中,保持a中的元素为出现次数最多的K个字符。

然后定义一个函数build,用于构建线段树:

Node* build(int l, int r, const string& s, int k) {
    if (l > r) return nullptr;
    Node* node = new Node{l, r, vector<pair<char, int>>{}, nullptr, nullptr};
    if (l == r) {
        node->freq.push_back(make_pair(s[l], 1));
        make_heap(node->freq.begin(), node->freq.end(), cmp);
    } else {
        int mid = l + (r - l) / 2;
        node->left = build(l, mid, s, k);
        node->right = build(mid+1, r, s, k);
        node->freq = node->left->freq;
        merge(node->freq, node->right->freq, k);
    }
    return node;
}

该函数递归地构建线段树,对于叶子节点,它的freq数组中只包含一个字符;对于非叶子节点,它的freq数组是它左右子树的freq数组合并后的结果。

最后,我们定义一个函数query,用于查询区间[L, R]中第K大的字符:

char query(int k, Node* node) {
    if (node->l == node->r) return node->freq.front().first;
    int sum = 0;
    for (auto x : node->left->freq) sum += x.second;
    if (k <= sum) {
        return query(k, node->left);
    } else {
        return query(k - sum, node->right);
    }
}

该函数递归地查询第K大的字符。首先统计左子树中出现次数最多的K个字符的出现次数之和,然后如果K在左子树中,就递归左子树,否则递归右子树。

时间复杂度
  • 局部更新:每次更新只涉及到一个叶子节点和它的祖先节点,因此时间复杂度是O(log n)。
  • 查找第K大的字符:由于堆的大小为K,所以合并两个freq数组的时间复杂度为O(K),从堆中取出最小元素的时间复杂度为O(log K),而查询的时间复杂度 O(log n),因此时间复杂度是 O(K log K + log n)。
完整代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;

bool cmp(const pair<char, int>& a, const pair<char, int>& b) {
    return a.second < b.second;
}

void merge(vector<pair<char, int>>& a, const vector<pair<char, int>>& b, int k) {
    for (auto x : b) {
        if (a.size() < k) {
            a.push_back(x);
            push_heap(a.begin(), a.end(), cmp);
        } else if (cmp(x, a.front())) {
            pop_heap(a.begin(), a.end(), cmp);
            a.back() = x;
            push_heap(a.begin(), a.end(), cmp);
        }
    }
}

struct Node {
    int l, r;
    vector<pair<char, int>> freq;
    Node *left, *right;
};

Node* build(int l, int r, const string& s, int k) {
    if (l > r) return nullptr;
    Node* node = new Node{l, r, vector<pair<char, int>>{}, nullptr, nullptr};
    if (l == r) {
        node->freq.push_back(make_pair(s[l], 1));
        make_heap(node->freq.begin(), node->freq.end(), cmp);
    } else {
        int mid = l + (r - l) / 2;
        node->left = build(l, mid, s, k);
        node->right = build(mid+1, r, s, k);
        node->freq = node->left->freq;
        merge(node->freq, node->right->freq, k);
    }
    return node;
}

char query(int k, Node* node) {
    if (node->l == node->r) return node->freq.front().first;
    int sum = 0;
    for (auto x : node->left->freq) sum += x.second;
    if (k <= sum) {
        return query(k, node->left);
    } else {
        return query(k - sum, node->right);
    }
}

int main() {
    string s = "abcdefgabcdefg";
    int n = s.size();

    int k = 3;
    Node* root = build(0, n-1, s, k);

    cout << "第" << k << "大的字符是:" << query(k, root) << endl;

    // 更新s[0]为'h'
    s[0] = 'h';
    // 局部更新树
    Node* node = root;
    while (node->l < node->r) {
        int mid = node->l + (node->r - node->l) / 2;
        if (0 <= mid) {
            node = node->left;
        } else {
            node = node->right;
        }
    }
    node->freq.front().second = 1;
    make_heap(node->freq.begin(), node->freq.end(), cmp);
    while (node != root) {
        node = node->left->freq.front().second == node->freq.front().second ? node->left : node->right;
        node->freq = node->left->freq;
        merge(node->freq, node->right->freq, k);
    }

    cout << "第" << k << "大的字符是:" << query(k, root) << endl;

    return 0;
}
参考资料