Bootstrap

【优选算法】优先级队列(堆)

在这里插入图片描述

一、最后一块石头的重量

题目描述
在这里插入图片描述

思路讲解
本题的思路就是使用堆也就是优先级队列,建立一个大堆,将数组中所有的数都添加到堆中,堆顶的元素就是最大的元素,每次取出两个最大的两个元素并将这两个数从堆中删除,若两个元素不相等,将两个元素差值的绝对值放到堆中,相等则不做处理,找堆中最大的两个元素重复上面的操作,直到堆中没有元素或只有一个元素,若堆中只有一个元素返回堆顶元素即可,堆中没有元素则返回0即可。

编写代码

class Solution {
public:
    int lastStoneWeight(vector<int>& stones) {
        priority_queue<int> us;

        for(auto x : stones)
        {
            us.push(x);
        }

        while(us.size() >= 2)
        {
            int left = us.top();
            us.pop();
            int right = us.top();
            us.pop();

            if(left != right)
                us.push(abs(left-right));
        }

        if(us.size() == 1)
            return us.top();
        else
            return 0;
    }
};

二、数据流中的第 K 大元素

题目描述
在这里插入图片描述

思路讲解
本题的最容易想到的思路就是将每一次add的时候就讲数组使用快速排序将数组变为有序,然后再返回第K大的数字,这样做每次add的时间复杂度为O(n * log 2 _2 2 n n n)。

排序后我们每次加入一个数字之前数组都是有序的,所以这里我们可以改变排序的方式,使用插入排序,这样做每次add的时间复杂度就变为O(n)。

本题更优解还是使用堆,建立一个大小为k的小堆,那么堆顶的元素就是第k大的元素,将数组中所有元素加入到堆中,之后add时就直接加入到堆中,若堆中的元素个数大于k个,我们就移除堆顶元素,使堆中元素的个数始终保持在k个,然后再返回堆顶的元素即可。

编写代码

class KthLargest {
public:
    priority_queue<int,vector<int>,greater<int>> pq;
    int _k;

    KthLargest(int k, vector<int>& nums) {
        _k = k;

        for(auto x : nums)
        {
            pq.push(x);
        }
    }
    
    int add(int val) {
        pq.push(val);

        while(pq.size()>_k)
        {
            pq.pop();
        }

        return pq.top();
    }
};

/**
 * Your KthLargest object will be instantiated and called as such:
 * KthLargest* obj = new KthLargest(k, nums);
 * int param_1 = obj->add(val);
 */

三、前K个高频单词

题目描述
在这里插入图片描述

思路讲解
通过本题的理解我们发现本题是TopK类型的题目,那么就可以使用堆来解决本题。

我们预处理一下原始字符串数组,用一个哈希表统计一下每一个单词和对应单词出现的频次。

创建一个大小为k的堆,并写一个仿函数,使得堆处理频次的时候是小堆,处理字典序的时候是大堆,也就是向堆中插入元素的频次与堆顶做对比,频次少的作为堆顶,向堆中插入元素的频次与堆顶相同时,字典序靠后的作为堆顶。

遍历哈希表循环向堆中添加元素,当堆中元素的个数超过k个时,移出堆顶元素,使堆中元素个数始终小于等于k个,直到遍历结束。

当遍历完成后,堆中就是我们需要的k个单词了,创建一个字符串数组,将堆顶元素一个一个的添加数组,由于堆顶的元素是出现频次最少的或是同频次下字典序靠后的,所以最后我们需要将字符串数组逆转才是最终的答案。

编写代码

    class Solution {
    public:
        typedef pair<string, int> PSI;

        struct Cmp
        {
            bool operator()(const PSI& p1 ,const PSI& p2)
            {
                // 出现频率高,且按字典顺序
                return p1.second > p2.second || 
                    (p1.second == p2.second && p1.first < p2.first);
            }
        };

        vector<string> topKFrequent(vector<string>& words, int k) {
            unordered_map<string, int> um;
            for(auto x : words)
                um[x]++;

            priority_queue<PSI,vector<PSI>,Cmp> tmp;
            for(auto psi : um)
            {
                tmp.push(psi);

                if(tmp.size()>k)
                {
                    tmp.pop();
                }
            }

            vector<string> ans;

            for(int i = 0 ; i < k ; i++)
            {
                ans.push_back(tmp.top().first);
                tmp.pop();
            }
            
            reverse(ans.begin(),ans.end());

            return ans;
        }
    };

四、数据流的中位数

题目描述
在这里插入图片描述

思路讲解
本题的最容易想到的思路就是将每一次add的时候就讲数组使用快速排序将数组变为有序,这样做每次add的时间复杂度为O(n * log 2 _2 2 n n n),find的时间复杂度就是O(1)。

排序后我们每次加入一个数字之前数组都是有序的,所以这里我们可以改变排序的方式,使用插入排序,这样做每次add的时间复杂度就变为O(n),find的时间复杂度就是O(1)。

本题更优解还是使用堆,我们想想一下将一个升序的数组对称的分为两半,若数组元素为奇数个,则左边数组比右边数组个数多一个,将左边小的一半数组元素放入到大堆之中,将右边大的一半数组元素放到小堆之中,那么这个大堆的堆顶元素就是左边数组中的最大元素(也是最右边元素),小堆的堆顶元素就是右边数组的最小元素(也是最左边元素)。所以若数组元素个数是奇数个,那么大堆堆顶元素就是中位数,若数组元素个数是偶数个,那么大堆堆顶元素与小堆堆顶元素相加再除以2就是中位数。

首先我们建立一个大堆(left)和一个小堆(right),我们认为大堆中元素的个数为n,小堆中的元素个数为m,我们规定两个堆元素个数的只会出现m == nm == n + 1这两种情况,我们根据两个堆中元素个数的不同和加入元素与堆顶元素的大小关系,使得add向不同的堆中插入元素,并且插入后使两个堆中元素个数满足上述情况之一。find则通过两个堆中元素的个数和得到不同的结果,奇数则left堆顶元素是中位数,偶数则left和right的堆顶元素相加除以2就是中位数了。

  • m == n
    1. num <= left.top() && left.size()==0,将num添加到left中。
    2. num > left.top(),将num添加到right中,再将right堆顶元素移到left中。
  • m == n + 1
    1. num < left.top() ,将num添加到left中,再将left堆顶元素移到right中
    2. num => left.top() ,将num添加到right中。

编写代码

class MedianFinder {
public:
    priority_queue<int> left;
    priority_queue<int,vector<int>,greater<int>> right;

    MedianFinder() {
        
    }
    
    void addNum(int num) {
        int leftnum = left.size() , rightnum = right.size();
        if(leftnum == 0)
        {
            left.push(num);
        }
        else if((leftnum + rightnum) % 2 == 0)
        {
            if(num <= right.top())
            {
                left.push(num);
            }
            else
            {
                left.push(right.top());
                right.pop();
                right.push(num);
            }
        }
        else
        {
            if(num >= left.top())
            {
                right.push(num);
            }
            else
            {
                right.push(left.top());
                left.pop();
                left.push(num);
            }
        }
    }
    
    double findMedian() {
        int leftnum = left.size() , rightnum = right.size();
        double ans = 0.0;
        if((leftnum + rightnum) % 2 == 0)
        {
            ans = (left.top() + right.top())/2.0;
        }
        else
        {
            ans = left.top();
        }
        return ans;
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

在这里插入图片描述

;