参考
MaxHeap.h
#include "Array.h"
template<class T>
class MaxHeap {
private:
Array<T> *data;
// 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
int leftChild(int index) {
return index * 2 + 1;
}
// 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
int rightChild(int index) {
return index * 2 + 2;
}
//使得添加的结点上升到满足二叉堆的性质
void shiftUp(int k) {
while (k > 0 && data->get(parent(k)) < data->get(k)) {
data->swap(k, parent(k));
k = parent(k);
}
}
//将元素下移到合适位置的方法
void shiftDown(int k) {
while (leftChild(k) < data->getSize()) {
int j = leftChild(k);
//--------------------------------------------------首先找到 k 的左右孩子结点中最大的结点
if (j + 1 < data->getSize() && data->get(j + 1) > data->get(j)) {
j = rightChild(k);
}
if (data->get(k) > data->get(j)) {
break;
}
data->swap(k, j);
k = j;
}
}
public:
class NoParent {
};
class Empty {
};
MaxHeap(int capacity) {
data = new Array<T>(capacity);
}
MaxHeap() {
data = new Array<T>();
}
MaxHeap(T arr[], int n){
data = new Array<T>(arr, n);
for (int i = parent(n - 1); i >= 0; --i) {
shiftDown(i);
}
}
int size() {
return data->getSize();
}
bool isEmpty() {
return data->isEmpty();
}
// 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
int parent(int index) {
if (index == 0) {
throw NoParent();
}
return (index - 1) / 2;
}
void add(T e) {
data->addLast(e);
shiftUp(data->getSize() - 1);
}
T findeMax() {
if (data->isEmpty()) {
throw Empty();
}
return data->get(0);
}
T extractMax() {
T ret = findeMax();
data->swap(0, data->getSize() - 1);
data->removeLast();
shiftDown(0);
return ret;
}
// 取出堆中的最大元素,并且替换成元素e
T replace(T e){
T ret = findeMax();
data->set(0, e);
shiftDown(0);
return ret;
}
void print() {
data->toPrint();
}
};
PriorityQueue .h
#include "Queue.h"
#include "MaxHeap.h"
template<class T>
class PriorityQueue : public Queue<T> {
private:
MaxHeap<T> *maxHeap;
public:
PriorityQueue() {
maxHeap = new MaxHeap<T>();
}
int getSize() {
return maxHeap->size();
}
bool isEmpty() {
return maxHeap->isEmpty();
}
T getFront() {
return maxHeap->findeMax();
}
void enqueue(T e) {
maxHeap->add(e);
}
T dequeue() {
return maxHeap->extractMax();
}
void print() {
std::cout << "Queue: size = " << maxHeap->size() << std::endl;
std::cout << "front ";
maxHeap->print();
std::cout << " tail" << std::endl;
}
};
LeetCode347 前k个高频元素
题目:给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
提示:
1、你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
2、你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。(不能使用排序解决)
3、题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。(不会出现2个不同的数字出现次数一样,否则前k个高频元素不唯一)
4、你可以按任意顺序返回答案。
方法分析(这种思想很重要!)
这个接法的技巧,就是通过一个 ComTemp 类,来定义什么是优先级。因为我们的优先队列是使用最大堆实现的,队首是优先级最大的元素,出队也是先出队优先级最大的元素。
那么我们将 数字 以及 数字出现的次数 分钟到ComTemp类的属性中,然后在这个类中定义数字出现次数越少,优先级越大,这样我们将k个元素ComTemp对象入队后,队首肯定是优先级最高(数字出现次数最低)的ComTemp对象,队列中其他ComTemp的优先级肯定低于队首ComTemp(数字出现次数比他大)。新来一个对象,如果这个ComTemp数字出现次数比队首大(优先级比队首小),我们将队首优先级最大的ComTemp出队(出现次数最少的ComTemp出队),将新的ComTemp对象加入。
这样,我们最后得到的长度为k的队列保存的ComTemp对象中,他们的数字就是原来数组中出现次数最大的前k个数字。
这种方法的时间复杂度是:O(nlogk)。当然,我们将数字以及他们的出现次数保存到TreeMap后,我们也可以根据出现次数进行排序,使用归并排序或者快速排序,最后也可以得到出现次数也前k的数字,但是这样时间复杂度为 O(nlogn),不满足题目要求。
代码如下:
#include <iostream>
#include <vector>
#include <unordered_map>
#include "PriorityQueue.h"
using namespace std;
class Solution {
public:
static vector<int> topKFrequent(vector<int> &nums, int k) {
unordered_map<int, int> record;
for (int i = 0; i < nums.size(); ++i) {
record[nums[i]] += 1;
}
for (unordered_map<int,int>::iterator iterator2 = record.begin();iterator2!= record.end();++iterator2) {
std::cout << iterator2->first << " " << iterator2->second << std::endl;
}
PriorityQueue<Freq> *priorityQueue = new PriorityQueue<Freq>();
//首先,先将k个Freq对象入队
/**
* 注意,pq队列的size初始值为0,当加入第一个元素的时候,size变为1,此时1<2,可以加入第二个元素,此时size变为2,2=2,无法加入第三个元素
* 也就是说,这里如果我们向添加k个元素,那么必须设置 pq.getSize()<k,而不是pq.getSize()<=k,这样会添加k+1个元素!!
*/
for (unordered_map<int, int>::iterator iterator1 = record.begin(); iterator1 != record.end(); ++iterator1) {
if (priorityQueue->getSize() < k) {
priorityQueue->enqueue(Freq(iterator1->first, iterator1->second));
} else if (iterator1->second > priorityQueue->getFront().freq) {
//k个数字已经放满,如果后面遍历到的tm中的数字出现的频率高于队首的数字出现的频率,
// 我们将队首的Freq对象出队,将新的数字构造成为Freq对象放入队列
priorityQueue->dequeue();
priorityQueue->enqueue(Freq(iterator1->first, iterator1->second));
}
}
//结束循环后,队列中保存的就是出现频率前k的数字以及其出现频率所构造成为的ComTemp对象
/**注意!!!
* 这里不能使用for循环,因为pq在出队的过程中,size会发生变化,这样便获取不到正确的pq中的数,
* 这里应该使用 while(!pq.isEmpty)
*/
vector<int> res;
while (!priorityQueue->isEmpty()) {
res.push_back(priorityQueue->dequeue().e);
}
return res;
}
private:
class Freq {
public:
int e, freq;
Freq(int e = 0, int freq = 0) {
this->e = e;
this->freq = freq;
}
bool operator<(const Freq &another) {
if (this->freq < another.freq) {
return false;
} else {
return true;
}
}
bool operator>(const Freq &another) {
if (this->freq > another.freq) {
return false;
} else {
return true;
}
}
};
};
如果我们想在N个元素中求出前M大的元素,同样可以使用最大堆实现的优先队列,只不过这里需要定义一个ComTemp,里面只有一个元素num,设置num越大,ComTemp越小。这样优先队列队首ComTemp的优先级最大,他得num也是整个队列最小。后面如果来一个新的ComTemp,且这个ComTemp的num大于队首的num,就将队首的num出队,将新的ComTemp添加进来即可!这样实现时间复杂度为O(nlogm)
如果是求N个元素中前M小的元素,同样可以使用最大堆实现的优先队列,只不过这次我们可以使用num直接进行比较,较大的放在优先队列的队首,后面如果有比他小的num,将队首元素出队,将新的num添加到队列中即可!(不需要构造ComTemp)。