692. Top K Frequent Words

本文介绍了一种高效算法,用于从非空单词列表中找出出现频率最高的 k 个元素,并按频率及字母顺序排列。利用哈希表统计频率,并采用优先队列进行排序。提供了两种实现方式:最大堆和最小堆。

Given a non-empty list of words, return the k most frequent elements.

Your answer should be sorted by frequency from highest to lowest. If two words have the same frequency, then the word with the lower alphabetical order comes first.

Example 1: Input: [“i”, “love”, “leetcode”, “i”, “love”, “coding”], k = 2
Output: [“i”, “love”]
Explanation: “i” and “love” are the two most frequent words.
Note that “i” comes before “love” due to a lower alphabetical order.

这题首先可以用哈希表统计每个词的频率,至于之后的基于自定义性质的排序问题,可以用priority_queue来解决,将比较函数写成自定义的要求就可以了。

方法一: max_heap

class compLess {
public:
    bool operator () (const pair<string, int>& a, const pair<string,int>& b) {
        if (a.second == b.second) {
            return a.first > b.first;
        }
        else {
            return a.second < b.second;
        }
    }

};

class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        unordered_map<string, int> hash;
        vector<string> res;
        for (string s : words) {
            if (hash.find(s) != hash.end()) {
                hash[s]++;
            }
            else {
                hash.emplace(s, 1);
            }
        }

        priority_queue<pair<string, int>, vector<pair<string, int>>, compLess> pq;
        for (auto s : hash) {
            pq.push(s);
        }

        for (int i = 0; i < k; i++) {
            res.push_back(pq.top().first);
            pq.pop();
        }
        return res;
    }
};

这里补充一下重载运算符的问题,不管是struct Node还是class Node 的写法,如果重载关系比较符,< > 之类的,当重载函数作为成员函数的时候,only one argument,就是写成:

struct Node {
    int freq;
    string str;
    Node (int f, string s) : freq(f), str(s) {}
    bool operator < (const Node& b) {
        return this->freq < b.freq;
    }
}

如果重载函数在结构体或者类之外的话,需要两个argument

#include <iostream>
#include <queue>

using namespace std;

struct Node{
    int x, y;
    Node( int a= 0, int b= 0 ):
        x(a), y(b) {}
};

bool operator<( Node a, Node b ){
    if( a.x== b.x ) return a.y> b.y;
    return a.x> b.x;
}

int main(){
    priority_queue<Node> q;

    for( int i= 0; i< 10; ++i )
    q.push( Node( rand(), rand() ) );

    while( !q.empty() ){
        cout << q.top().x << ' ' << q.top().y << endl;
        q.pop();
    }

    getchar();
    return 0;
}

这个函数就是把max priority_queue 通过重载< 变成了min priority_queue;

第二个想要补充说明的就是priority_queue的重载问题。
默认的是max heap, 默认的比较是 < , 如果想要变成min heap, 可以重载<, 将名义上的< 变成 >, 这种重载的时候不需要再声明该priority_queue的时候说明,所以声明的时候还是只要一个或者两个argument就行。
例如:

priority_queue<int, vector<int>> pq;

但也可以重载比较函数(默认的是less,也可以通过写成greater< int>改成小堆, 但这种greater和less只对于默认的built-in类型可以,自定义类型需要自定义比较函数)。
例如上述方法一中的,class compLess;
将上面的重载方法也可以依次改写为:

#include <iostream>
#include <queue>

using namespace std;

struct Node{
    int x, y;
    Node( int a= 0, int b= 0 ):
        x(a), y(b) {}
};

struct cmp{
    bool operator() ( Node a, Node b ){
        if( a.x== b.x ) return a.y> b.y;

        return a.x> b.x; }
};

int main(){
    priority_queue<Node, vector<Node>, cmp> q;

    for( int i= 0; i< 10; ++i )
    q.push( Node( rand(), rand() ) );

    while( !q.empty() ){
        cout << q.top().x << ' ' << q.top().y << endl;
        q.pop();
    }

    getchar();
    return 0;
}

好,说回本题,在时间和空间上都可以更加优化一点,因为我们只需要前k个,所以在priority_queue里没有必要把所有的都插入,因为当元素越多,插入的时间就越长O(log n)嘛。所以我们可以只 保留k个元素在里面,这个时候,就需要用最小堆了。因为每多插入一个,就要扔掉一个,只有明确比这四个小的才可以放心扔掉。

方法二:min heap

class compLess {
public:
    bool operator () (const pair<string, int>& a, const pair<string,int>& b) {
        if (a.second == b.second) {
            return a.first < b.first;
        }
        else {
            return a.second > b.second;
        }
    }

};

class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        unordered_map<string, int> hash;
        for (string s : words) {
            if (hash.find(s) != hash.end()) {
                hash[s]++;
            }
            else {
                hash.emplace(s, 1);
            }
        }

        priority_queue<pair<string, int>, vector<pair<string, int>>, compLess> pq;
        for (auto s : hash) {
            if (pq.size() < k) {
                pq.push(s);
            }
            else {
                auto currmin = pq.top();
                if (currmin.second < s.second) {
                    pq.pop();
                    pq.push(s);
                }
                else if (currmin.second == s.second) {
                    if (currmin.first > s.first) {
                        pq.pop();
                        pq.push(s);
                    }    
                }
            }
        }

        vector<string> res(k);
        for (int i = k - 1; i >= 0; i--) {
            res[i] = pq.top().first;
            pq.pop();
        }
        return res;
    }

};

leetcode测速,方法一26ms, 方法二19ms.

<think>嗯,用户让我介绍一下POJ3368这个题目,题目名称是Frequent values。我需要先回想一下这个题目的具体内容。记得这是一个关于数组查询的问题,通常出现在编程竞赛中,比如ACM之类的。首先,我需要确定题目的要求和约束条件。 题目大意应该是给定一个非递减的有序整数数组,然后有多个查询,每个查询问的是在某个区间[left, right]内,出现次数最多的数的频次是多少。对吧?比如数组可能是这样的:-1, -1, 1, 1, 1, 1, 3, 10, 10, 10。这时候如果查询整个数组,答案应该是4,因为1出现了4次。 接下来,我需要考虑如何高效地解决这个问题。因为数组是非递减的,所以相同的数字一定是连续存储的。这一点很关键,可能可以用来优化算法。比如,可以把数组分成若干个块,每个块存储相同的数字,并记录每个块的起始和结束位置,以及该块的长度。 对于每个查询[left, right],可能的情况是,这个区间覆盖了多个相同数字的块,或者部分覆盖了左右两个块,中间夹着完整的块。这时候,最大的频次可能出现在这三个部分中的一个:左边被部分覆盖的块的数目,右边被部分覆盖的块数目,或者中间完整块的最大频次。因此,问题转化为如何快速找到这三个部分的最大值。 这时候,可能需要使用线段树或者稀疏表(Sparse Table)来处理区间最值查询。不过,由于数组已经是有序的,可以考虑预处理每个位置的所属块的信息,例如每个位置所在的块的左边界、右边界,以及该块的长度。然后,对于每个查询,先处理左右两边可能的部分块,再对中间完整的块使用RMQ(Range Maximum Query)来找到最大频次。 不过,用户可能希望得到更具体的步骤和实现方法。比如,如何预处理数组?例如,可以将数组转换为一个结构数组,记录每个块的起始索引、结束索引和出现次数。比如,原数组为-1, -1, 1, 1, 1, 1, 3, 10, 10, 10,那么预处理后的结构可能是:块数组为每个元素的值、start、end、count。然后,对于每个查询,确定left和right所在的块,然后计算中间的完整块的最大count,以及左右部分块的count,然后取最大值。 比如,假设查询的区间跨越了三个块,左边的块可能被截断,中间有若干个完整的块,右边也可能被截断。这时候左边的部分长度是min(块右边界, right) - left +1,右边的部分长度是right - max(块左边界, left)+1。中间的块可以通过RMQ来查询最大值。 那么预处理阶段,可以将每个块的出现次数存储在一个数组中,并建立RMQ结构,比如稀疏表。这样,当处理中间完整块的查询时,可以在O(1)或O(log n)的时间得到最大值。 另外,如何确定查询区间跨越哪些块?可能需要预先记录每个位置属于哪个块,可以用一个数组来记录每个索引对应的块的左边界和右边界,或者块的编号。例如,对于每个i,left_bound[i]表示i所在的块的左边界,right_bound[i]表示右边界,count[i]表示该块的长度。这样,当给定left和right时,可以通过left_bound和right_bound数组快速找到左右边界的块。 然后,如果left所在的块和right所在的块是同一个块,那么整个查询区间的频次就是right - left +1。否则,需要计算左边块的剩余数目,右边块的数目,以及中间块的RMQ结果,三者取最大值。 举个例子,假设原数组是1,1,1,2,2,3,3,3,3。查询区间是[2,7]。这时候,数组的索引可能是从0开始还是从1开始?需要确定题目的具体输入格式。假设数组索引是1-based的话,left=2,right=7对应的数值是1,2,2,3,3,3。这时候左边的块是第二个1(假设块0是1的块,结束于索引3),而块1是2的块,结束于索引5,块2是3的块。那么查询区间覆盖了块0的部分(索引2-3),块1的全部(索引4-5),和块2的部分(索引6-7)。左边部分的长度是3-2+1=2?或者可能要看具体的块结构。 可能这个例子中,原数组是分成三个块:1的块(索引1-3,长度3),2的块(4-5,长度2),3的块(6-9,长度4)。查询区间是2到7,所以左边块是1的块中的索引2-3,长度是2;中间是否有块?这里块1是2的块,索引4-5,在查询的2-7范围内,所以中间块是块1的全部长度2;右边块是3的块的索引6-7,长度2。所以最大的是中间块的可能吗?或者要看这三个部分的最大值。中间块的长度是2,左边的部分是2,右边部分是2,但中间可能还有其他块?或者这个例子中,中间可能没有完整的块。这个时候可能中间没有完整的块,所以最大值可能在左中右三个部分中的最大值。此时最大值是2,但原数组中的块3的长度是4,但查询的右边部分只取了前两个,所以可能这里的处理是:中间的完整块是块1,它的长度是2。所以此时最大值可能是2,但原数组中的块3的最大长度是4,但因为查询只到7,所以右边的部分可能只是2。或者我的分析可能有错误。 这说明具体的实现需要仔细处理左右边界的块,以及中间的完整块的最大值。 总结思路: 1. 预处理数组,将其分成若干个块,记录每个块的起始、结束位置和出现次数。 2. 对每个查询[left, right],找到left所在的块和right所在的块: a. 如果两个块相同,则结果为right - left +1。 b. 否则,计算左边块在[left, right]中的数目,右边块的数目,以及中间所有完整块的最大数目,三者取最大。 3. 中间完整块的最大数目可以通过预处理后的RMQ结构来快速查询。 因此,实现的关键步骤包括: - 预处理块结构。 - 建立RMQ结构(比如稀疏表)来快速查询任意区间的最大值。 - 对每个查询进行分情况处理。 现在,用户可能需要具体的实现方法或者步骤。例如,如何预处理块结构? 例如,遍历原数组,记录每个块的起始位置。例如,初始化start=0,然后遍历数组,当元素变化时,记录前一个块的结束位置为i-1,count为i - start。然后将这些信息保存到块数组中。同时,对于每个位置i,可以记录它所属的块的信息,比如块的索引,或者块的起始和结束位置。 接着,建立RMQ的结构,块数组中的每个块的出现次数作为值,建立稀疏表,这样可以在O(1)的时间内查询任意块区间的最大值。 当处理查询时,首先找到left所在的块,和right所在的块。例如,使用二分查找在块数组中找到对应的块。假设块是按顺序排列的,每个块的起始和结束是递增的,所以可以二分查找left所在的块。 比如,块数组中的每个块有start和end属性。对于给定的left,找到最大的块索引,使得start <= left <= end。同样处理right。 例如,块数组是按顺序存储的,所以可以用二分查找来确定left和right所在的块。 例如,left所在的块是p,right所在的块是q。 如果p == q,那么整个区间都在一个块中,结果是right - left +1。 否则,左边的数目是块p的end - left +1;右边的数目是right -块q的start +1;中间的块是p+1到 q-1,如果存在的话,此时需要用RMQ查询这些块的最大count。 然后,这三者的最大值就是答案。 这样,整个算法的时间复杂度是预处理O(n),每个查询O(log n) + O(1)(如果RMQ使用稀疏表的话)。 现在,用户可能需要具体的例子来说明这个过程。 举个例子,原数组是:-1, -1, 1, 1, 1, 1, 3, 10, 10, 10。对应的块数组可能是: 块0: start=0, end=1, count=2(数值-1) 块1: start=2, end=5, count=4(数值1) 块2: start=6, end=6, count=1(数值3) 块3: start=7, end=9, count=3(数值10) 假设有一个查询区间是[3, 8],即数组中的索引从3到8(假设是0-based还是1-based?需要明确题目的输入)。假设题目中的数组是1-based,那么索引3到8对应的数值是1,1,1,3,10,10。块的情况是: left=3属于块1(start=2,end=5),所以块p=1。right=8属于块3(start=7,end=9),所以块q=3。p < q。 左边部分数目:块p的end - left +1 →5-3+1=3(数值1的三个元素)。 右边部分数目:right -块q的start +1 →8-7+1=2(数值10的两个元素)。 中间块是块2(p+1=2到q-1=2),所以查询块2的count是1。中间的最大是1。所以三者最大值是3,所以答案应该是3。 但实际在查询区间中,数值1出现3次,数值10出现2次,中间块3的数值是1次。但原数组中的块1的count是4,但在查询区间中被截断为3次。所以答案正确。 这样,算法是正确的。 那如何处理中间的块呢?比如,如果有多个中间块,如何快速找到它们的最大count?这时候,需要块数组的count数组,用RMQ结构预处理,这样在查询块区间p+1到q-1时,可以快速得到最大值。例如,块数组的count数组是[2,4,1,3],那么建立稀疏表之后,查询区间p+1=2到q-1=2(即块2)的max是1。这样就能得到中间的最大值。 现在,用户可能需要知道具体的实现步骤,包括如何预处理块数组,如何建立RMQ,如何处理查询。 例如,预处理块数组的步骤: 1. 遍历原数组,记录每个块的start,end,count。例如: 初始化start=0,current_val = arr[0]。然后,从i=1到n-1遍历数组: 如果arr[i] != current_val,则记录一个块:start到i-1,count=i-start。然后更新start=i,current_val=arr[i]。遍历结束后,记录最后一个块。 2. 同时,可以建立一个数组counts,保存每个块的count值,用于RMQ。 3. 对于每个位置i,可以记录其所属块的索引。或者,建立一个数组pos_to_block,其中pos_to_block[i]是块索引,这样给定一个位置i,可以快速找到属于哪个块。或者,可以用二分查找块数组中的start和end来确定。 例如,块数组是按start递增的顺序排列的,所以可以用二分查找找到最大的块索引,使得块的start <= i <= end。 这部分可能有点复杂,但可以通过将每个块的start和end保存,然后在查询时用二分查找来确定块。 现在,针对每个查询[left, right]: 步骤: 1. 找到left所在的块p:最大的块索引,其中start <= left <= end. 2. 找到right所在的块q:最大的块索引,其中start <= right <= end. 3. 如果p == q,则结果为right - left +1. 4. 否则: a. left_part = block[p].end - left +1 b. right_part = right - block[q].start +1 c. mid_max = 查询块p+1到块q-1之间的最大count(使用RMQ) d. 结果取这三个中的最大值。 现在,问题是如何高效地找到块p和块q。因为块是按顺序排列的,所以块的start数组是递增的。因此,可以用二分查找在块的start数组中找到最后一个start <= pos的块。 例如,块的start数组为starts = [0,2,6,7]。要找位置3所在的块,可以用lower_bound或upper_bound的方法。例如,找到最大的i,使得 starts[i] <=3。starts[0]=0,starts[1]=2,starts[2]=6。所以,3 >= starts[1]=2,但 < starts[2]=6。因此,块1的start是2,end是5,包含位置3。所以块p=1。 这可以通过二分查找实现,例如,使用bisect_right来找插入位置,然后减一。 例如,在Python中,可以用bisect.bisect_right(starts, pos) -1。但需要确保块的范围是否包含pos,因为start可能<=pos<=end。因此,正确的做法可能需要更复杂的查找,或者将块的start和end存储为两个数组,或者存储块列表,每个块包含start和end属性。 另一种方法是,将每个块的start和end记录下来,然后对每个查询位置,遍历块列表,直到找到包含该位置的块。但这样在块很多的情况下效率不高,所以二分查找是更好的选择。 因此,块的start数组是按递增顺序排列的,所以可以建立一个数组block_starts,其中block_starts[i]是第i块的start。然后,对于给定的pos,找到最大的i,使得 block_starts[i] <= pos。然后检查该块的end是否>= pos。如果是,则该块即为pos所在的块。否则,需要调整。 但如何确保找到的块的end >= pos?假设块是按顺序排列,且无重叠,那么块i的start <=块i+1的start,并且块i的end +1 =块i+1的start。所以,如果块i的start <= pos <=块i的end,那么块i就是正确的。 所以,假设块列表是按顺序排列的,可以执行以下步骤: 对于pos,找到最大的i,使得 block_starts[i] <= pos. 然后,检查该块的end是否 >= pos。如果是,则该块即为正确块;否则,可能需要处理异常情况,但根据问题描述,数组是连续的,每个pos属于且仅属于一个块,所以不存在这种情况。因此,这样的二分查找是可行的。 综上,解决这个问题的步骤包括: 预处理阶段: 1. 将原数组分块,记录每个块的start、end和count。 2. 建立块数组,并提取count数组用于RMQ预处理。 3. 对count数组进行稀疏表或其他RMQ结构的预处理。 查询阶段: 对每个查询[left, right]: 1. 确定left所在的块p和right所在的块q。 2. 如果p == q,则结果为right - left +1. 3. 否则: a. 计算left_part和right_part. b. 查询中间块的最大count. c. 取三者最大值作为结果。 现在,关于实现中的细节问题: 例如,当中间没有块时(即q = p+1),这时候中间块的区间是空的,所以mid_max可以视为0或者不考虑。此时最大值是left_part和right_part中的较大者。 例如,在查询中,如果p+1 > q-1,则中间块不存在,所以mid_max不需要考虑。此时结果取left_part和right_part的最大值。 此外,块的索引是从0开始的。 关于预处理RMQ结构,比如使用稀疏表: 稀疏表的构建时间复杂度是O(n log n),查询时间是O(1)。对于每个块i的count,建立稀疏表后,可以在O(1)的时间内查询任意区间的max。 例如,对于块数组的count数组,建立稀疏表st,其中st[k][i]表示从i开始,长度为2^k的区间的最大值。预处理时,需要填充这个表,然后查询时,对于区间[l, r],计算k=floor(log2(r-l+1)),然后取max(st[k][l], st[k][r-2^k+1])。 现在,用户可能需要知道这个问题的代码结构的大致框架。 例如,代码的大致步骤: 读取n和q(假设n是数组长度,q是查询次数)。 读取数组a。 预处理分块: 初始化块列表blocks,count数组。 current_val = a[0], start=0. 遍历数组i从1到n-1: if a[i] != current_val: end = i-1 count = end - start +1 blocks.append( (start, end, count) ) current_val = a[i] start = i 添加最后一个块: blocks.append( (start, n-1, n-1 - start +1) ) 建立block_starts数组: block_starts = [ block[0] for block in blocks ] 建立count数组: counts = [ block[2] for block in blocks ] 预处理稀疏表: 建立log_table数组,log_table[i] = floor(log2(i)). 建立st表,其中st[k][i]表示从i开始,长度为2^k的区间的最大值。 然后,处理每个查询: 对于每个查询left, right(注意题目中的索引是否是1-based,可能需要调整,比如原题中可能输入是1-based的数组索引,需要转换为0-based): left -=1 right -=1 使用二分查找在block_starts中找到p和q: p = bisect.bisect_right(block_starts, left) -1 然后检查blocks[p][0] <= left <= blocks[p][1] 同理找到q。 如果 p == q: result = right - left +1 else: left_part = blocks[p][1] - left +1 right_part = right - blocks[q][0] +1 mid_max = 0 if p+1 <= q-1: mid_max = query_rmq(st, counts, p+1, q-1) result = max(left_part, right_part, mid_max) 输出result. 这样,整个代码的大致结构就清晰了。 可能需要注意的边界情况: 例如,数组中的所有元素都相同的情况。此时只有一个块,任何查询的结果都是right-left+1。 或者查询区间刚好在块的开头或结尾的情况。 或者当中间块不存在时如何处理。 此外,关于二分查找的实现,必须确保正确找到对应的块。例如,在Python中可以使用bisect模块中的bisect_right函数。 例如,block_starts是一个递增的数组,要找最大的i使得block_starts[i] <= pos。bisect_right返回插入的位置,所以减一即可。但必须确保block_starts中的元素是严格递增的,且每个块的start是严格递增的,这在这道题目的情况下成立,因为数组是非递减的,所以每个块的start一定比前一个块的start大。 综上,这个问题的解决方法是基于分块和RMQ的结合,利用数组有序的特性,将问题分解为几个部分,分别处理,从而在O(n)预处理和O(1)或O(log n)查询时间内得到结果。这应该是比较高效的解决方案,适用于较大的数据量。</think>### POJ3368 Frequent values 问题详解 #### 问题描述 给定一个**非递减有序整数数组**,处理多个查询,每个查询要求找出区间 $[L, R]$ 内出现次数最多的数的**频次**。例如: - 数组:$[-1, -1, 1, 1, 1, 1, 3, 10, 10, 10]$ - 查询 $[1, 10]$ 的答案是 $4$(数字 $1$ 出现 $4$ 次)。 #### 关键思路 1. **数组分块**:将连续相同元素划分为块,记录每块的起始、结束位置和频次。 2. **区间最值查询(RMQ)**:预处理块频次,构建稀疏表以快速查询任意块区间的最大频次。 3. **分情况处理查询**:根据查询区间覆盖的块情况,分别计算左半部分、右半部分和中间完整块的最大频次。 --- #### 算法步骤 ##### 1. 预处理分块 - **遍历数组**,将连续相同元素合并为块。 - **记录每块信息**:起始索引、结束索引、频次。 - 示例: - 数组 $[-1, -1, 1, 1, 1, 1, 3, 10, 10, 10]$ 分块为: $$块_0: [0,1], 频次=2 \quad 块_1: [2,5], 频次=4 \quad 块_2: [6,6], 频次=1 \quad 块_3: [7,9], 频次=3$$ ##### 2. 构建稀疏表(Sparse Table- **预处理块频次数组**,构建稀疏表以支持 $O(1)$ 时间复杂度的区间最值查询。 ##### 3. 处理查询 对每个查询 $[L, R]$: - **确定边界块**:找到 $L$ 所在的块 $p$ 和 $R$ 所在的块 $q$。 - **分情况处理**: - **同一块**:结果为 $R - L + 1$。 - **不同块**: - 计算左半部分频次:$块_p.end - L + 1$。 - 计算右半部分频次:$R - 块_q.start + 1$。 - 查询中间完整块的最大频次(使用稀疏表)。 - 取三者最大值。 --- #### 示例分析 以查询 $[3,8]$(数组索引从 $0$ 开始)为例: - **数组分块**: $$块_0: [-1], 块_1: [1], 块_2: [3], 块_3: [10]$$ - **确定边界块**: - $L=3$ 属于块$_1$(范围 $[2,5]$)。 - $R=8$ 属于块$_3$(范围 $[7,9]$)。 - **计算各部分频次**: - 左半部分:$5 - 3 + 1 = 3$(数字 $1$)。 - 右半部分:$8 - 7 + 1 = 2$(数字 $10$)。 - 中间块:块$_2$ 频次为 $1$。 - **结果**:$\max(3, 2, 1) = 3$。 --- #### 代码实现要点(伪代码) ```python # 预处理分块 blocks = [] current_val = arr[0] start = 0 for i in range(1, n): if arr[i] != current_val: end = i-1 blocks.append( (start, end, end-start+1) ) current_val = arr[i] start = i blocks.append( (start, n-1, n-1-start+1) ) # 构建稀疏表 counts = [block[2] for block in blocks] build_sparse_table(counts) # 处理查询 for each query (L, R): p = find_block(L) # 二分查找确定块 q = find_block(R) if p == q: ans = R - L + 1 else: left_part = blocks[p][1] - L + 1 right_part = R - blocks[q][0] + 1 mid_max = query_sparse_table(p+1, q-1) # 中间块最大频次 ans = max(left_part, right_part, mid_max) ``` --- #### 复杂度分析 - **预处理**:分块 $O(n)$,稀疏表构建 $O(n \log n)$。 - **查询**:每次 $O(\log n)$(二分查找块) + $O(1)$(稀疏表查询)。 #### 总结 通过分块与稀疏表结合,该算法高效解决了区间频次查询问题,适用于大规模数据。核心在于利用数组有序性,将区间分解为左、中、右三部分,分别计算最大值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值