数据结构与算法-23.前K个高频元素

23、前K个高频元素

题目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pRGX4RO3-1637846179690)(image-20211125204520689.png)]

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】

把这个一维的数组像下图这样排列(是不是像一个树)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r2XepN9f-1637846179693)(1637844949228.jpg)]

既然像树,那么父节点怎么和子节点连接起来呢,比如第二层的那个8,他的下标是1,那2的下标不就是1*2+1 = 3嘛,5的下标不就是1*2+2 = 4吗,其他的都是这样滴,那这样我们就可以构造一颗假树了。

在构造假树的时候,从下往上构造,顺便把最大的数放在根节点,比如左下角这一块,把2和3互换位置,这样最后构造的假树的最顶点就是整个树最大的数了,就是像下面这样。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mpZarwpR-1637846179695)(1637845720929.jpg)]

注意,这里仅仅是巧合,好像显得每个根节点都是最大的,其实我们只能保证顶点的根节点是最大的。

先设置一个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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值