23、前K个高频元素
题目
23.0、基于快排
先遍历一遍数组,用哈希映射来记录每个值对应的频率。
把哈希映射转化为vector<pair<int,int>>或者二维数组的格式。
然后就可以使用和查找数组中第K大元素相同的方法来找到前K大频率的分割下标。
private:
//产生随机数,将该随机下标的数与最后一个数调换位置,然后将数组分为左右两部分,返回分界线的下标
int randomPartition(vector<pair<int, int>>& dataPair, int leftIndex, int rightIndex)
{
srand((unsigned int)time(0));
int randnum = rand() % (rightIndex - leftIndex + 1) + leftIndex;
swap(dataPair[randnum], dataPair[rightIndex]);
int flagIndex = leftIndex;
for (int i = leftIndex; i < rightIndex; i++)
if (dataPair[i].second >= dataPair[rightIndex].second)
{
swap(dataPair[i], dataPair[flagIndex]);
flagIndex++;
}
swap(dataPair[rightIndex], dataPair[flagIndex]);
return flagIndex;
}
//快速选择
void quickSelect(vector<pair<int, int>>& dataPair, int leftIndex, int rightIndex, int k)
{
int flagIndex = randomPartition(dataPair, leftIndex, rightIndex);
if (flagIndex == k)
return;
else
flagIndex < k ? quickSelect(dataPair, flagIndex + 1, rightIndex, k) : quickSelect(dataPair, leftIndex, flagIndex - 1, k);
}
public:
//快速排序
vector<int> topKFrequent(vector<int>& nums, int k)
{
vector<int> result;
unordered_map<int, int> dataMap;
for (auto& i : nums) //统计个数
dataMap[i]++;
vector<pair<int, int>> dataPair(dataMap.begin(), dataMap.end());//把哈希映射变为一个数组
quickSelect(dataPair, 0, dataPair.size() - 1, k - 1);
for (int i = 0; i < k; i++)
result.push_back(dataPair[i].first);
return result;
}
时间复杂度:O(n)具体证明,参照算法导论。
空间复杂度:O(n)这里的n都指统计完频率后的个数n
23.1、优先队列(大根堆)
先遍历一遍数组,用哈希映射来记录每个值对应的频率。
把哈希映射转化为vector<pair<int,int>>或者二维数组的格式。(先序步骤和上面的一样)
手撸大根堆:
对于序列【5,8,3,2,5,3,8,1,3】
其下标为【0,1,2,3,4,5,6,7,8】
把这个一维的数组像下图这样排列(是不是像一个树)
既然像树,那么父节点怎么和子节点连接起来呢,比如第二层的那个8,他的下标是1,那2的下标不就是1*2+1 = 3嘛,5的下标不就是1*2+2 = 4吗,其他的都是这样滴,那这样我们就可以构造一颗假树了。
在构造假树的时候,从下往上构造,顺便把最大的数放在根节点,比如左下角这一块,把2和3互换位置,这样最后构造的假树的最顶点就是整个树最大的数了,就是像下面这样。
注意,这里仅仅是巧合,好像显得每个根节点都是最大的,其实我们只能保证顶点的根节点是最大的。
先设置一个heapSize表示需要维护节点的多少
我们把最上面的8和最下面的2互换位置,heapSize减1,表示把最大的8删除了(heapSize-1也就管不到一维数组最后面的8了,所以就当是删除了)
进行互换后,需要对树重新进行维护,保证顶点是最大的
以此类推,直到删够K个节点,就得到了前K个高频元素了。
vector<int> topKFrequent2(vector<int>& nums, int k)
{
vector<int> result;
unordered_map<int, int> dataMap;
for (auto& i : nums) //统计个数
dataMap[i]++;
vector<pair<int, int>> dataPair(dataMap.begin(), dataMap.end());//把哈希映射变为一个数组
int heapSize = dataPair.size();
buildMaxHeap(dataPair, heapSize);
for (int i = dataPair.size() - 1; i >= (int)dataPair.size() - k; i--)
{
result.push_back(dataPair[0].first);
swap(dataPair[i], dataPair[0]);
heapSize--;
maxHeapify(dataPair, 0, heapSize);
}
return result;
}
void buildMaxHeap(vector<pair<int, int>>& dataPair, int heapSize)
{
for (int i = heapSize / 2; i >= 0; i--)
maxHeapify(dataPair, i, heapSize);
}
void maxHeapify(vector<pair<int, int>>& dataPair, int root, int heapSize)
{
int leftChild = root * 2 + 1, rightChild = root * 2 + 2, largestChild = root;
if (leftChild<heapSize && dataPair[leftChild].second>dataPair[largestChild].second)largestChild = leftChild;
if (rightChild<heapSize && dataPair[rightChild].second>dataPair[largestChild].second)largestChild = rightChild;
if (largestChild != root)
{
swap(dataPair[root], dataPair[largestChild]);
maxHeapify(dataPair, largestChild, heapSize);
}
}
时间复杂度:O(n log n)在维护的时候,只需要顺着一条树枝维护,所以消耗是log n
空间复杂度:O(n)