Leetcode--堆類型題總結(單堆與雙堆)

目錄

 

1.C++中的堆實現

2.單堆問題

3.雙堆問題


1.C++中的堆實現

可以直接用優先級隊列priority_queue

默認是大頂堆 priority_queue<int> maxheap

小頂堆 priority_queue<int,vector<int>,greater<int>> minheap

也可以使用 multiset (使用multiset的情況一般爲 需要從堆中刪除元素,因爲priority_queue的方法中沒有 erase方法)

默認是大頂堆 multiset<int> maxheap

小頂堆 multiset<int,greater<int>> minheap

2.單堆問題

指通過一個堆就可以解決的問題

一般這種問題都具有以下特點

求解第/前 k個最大,最小或是最頻繁的元素;都可以使用堆來實現 (而不用通過排序實現)

模式

確定大頂堆還是小頂堆

比如求 第K個最大元素,我們就用 大小爲K的小頂堆,遍歷數組完畢後,小頂堆堆頂元素即爲第K個最大元素

遍歷數組,壓入小頂堆,判斷小頂堆的元素個數,如果大於k,則彈出,保證小頂堆內元素個數始終是 k個

 

對於最頻繁或是最不頻繁的元素問題:可以首先結合 pair<int,int>對組 通過遍歷統計,然後以 對組爲 堆中元素進行 入堆

priority_queue<pair<int,int>,vector<pair<int,int>>,less<pair<int,int>>>  maxheap

 

比如:尋找第k個最大元素

求第k個最大元素,我們保留下來前k個最大元素即可,而前k個元素中我們又要最小的一個,所以我們可以使用小頂堆

保證小頂堆元素內個數不超過k即可,因爲 隨着我們壓入元素,堆的大小增大,而小元素 一定在堆頂,所以 當堆的大小超過k時,就會 pop出去小元素,最後堆中剩下前k個大元素

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        
        for(auto i : nums)
        {
            minheap.push(i);
            if(minheap.size() > k)
            {
                minheap.pop();
            }
        }
        return minheap.top();
        
    }
private:
    priority_queue<int,vector<int>,greater<int>> minheap;
};

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) 
    {
        unordered_map<int,int> map;
        vector<int> res;
        for(int i = 0; i < nums.size(); i++)//統計每個元素的出現的頻率
        {
            map[nums[i]]++;
        }
        priority_queue<pair<int,int>,vector<pair<int,int>>,less<pair<int,int>>> pq; //用大頂堆來記錄 頻率和對應元素,
        //用pair的第一個元素代表頻率,第二個元素代表對應該頻率對應的元素
        
        for(auto it = map.begin(); it != map.end(); it++)
        {
            pq.push(make_pair(it->second,it->first));
            if(pq.size() > map.size()-k)
            {
                res.push_back(pq.top().second);
                pq.pop();
            }
        }
        return res;
    }
};

3.雙堆問題

指通過兩個堆相互配合解決問題

特點:

被告知,我們拿到一大把可以分成兩隊的數字。怎麼把數字分成兩半?使得:小的數字都放在一起,大的放在另外一半。雙堆模式就能高效解決此類問題。然後通過小頂堆尋找最小數據,大頂堆尋找堆中最大數據

這樣中位數就可以通過 小頂堆和大頂堆堆頂元素求出

模式:

比如求 數據流中的 中位數,因爲 數據流一直在增加,所以如果採用排序,那麼每一次增加元素後均需要再次排序,時間複雜度過高; 由於中位數 只需要知道中間兩個數(或一個數)即可求出

我們可以使用 大頂堆 來存儲 數據流中的 一半較小元素;用 小頂堆來存儲 數據流中的 一般較大元素;

且保證 大頂堆的大小 大於 等於 小頂堆的大小,這樣 如果 數據流大小爲奇數,則返回 大頂堆 的堆頂元素即可,如果數據流大小爲偶數,則 大頂堆和小頂堆元素取平均即可

如何實現?

//已知此時數據流元素個數爲2k,大頂堆中存儲較小元素,大頂堆存儲較大元素

maxheap.push(val);//此時堆頂爲大元素

將大元素 壓入 小頂堆中

minheap.push(maxheap.top())

maxheap.pop()//從大頂堆中刪除

//且要保證 大頂堆的大小不小於 小頂堆大小

while(maxheap.size() < minheap.size()) {將 小頂堆中元素壓入大頂堆}

//最後通過驗證數據流大小 來求中位數即可

class MedianFinder {
public:
    /** initialize your data structure here. */
    MedianFinder() {
        
    }
    
    void addNum(int num) {
        
        maxheap.push(num);//將所有數壓入大頂堆
        
        minheap.push(maxheap.top());// 將 大頂堆中的最大元素壓入最小堆
        maxheap.pop();
        
        //同時要保持,maxheap的大小比 minheap大或者相等
        if(maxheap.size() < minheap.size())
        {
            maxheap.push(minheap.top());
            minheap.pop();
        }
    
    }
    
    double findMedian() {
        
        if(minheap.size() != maxheap.size())
        {
            return maxheap.top();
        }
        return (minheap.top()+maxheap.top())*0.5;
    }
    
private:
    priority_queue<int> maxheap;//大頂堆 ,裝 小一部分的數,始終保持大頂堆中多一個數
    priority_queue<int,vector<int>,greater<int>> minheap;//小頂堆,裝 比較大一部分的數
    
};

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

這裏由於是滑動窗口,也就是說 兩個堆裏面的總元素是有刪減的,所以不能再用 priority_queue,因爲其中沒有 erase方法

我們可以使用 multiset來實現堆內元素的刪減

然後結合固定滑動窗口法即可(逐漸改變滑動窗口內元素即可)

詳細代碼如下:

class Solution {
public:
    vector<double> medianSlidingWindow(vector<int>& nums, int k) {
        
        vector<double> res;
        
        for(int i = 0; i < k; i++)
        {
            maxheap.insert(nums[i]);
            
            minheap.insert(*maxheap.begin());
            maxheap.erase(maxheap.begin());
            
            if(maxheap.size() < minheap.size())
            {
                maxheap.insert(*minheap.begin());
                minheap.erase(minheap.begin());
            }
        }//初始的大頂堆與小頂堆
        
        if(k%2 == 0)
        {
            res.push_back((*minheap.begin() * 0.5 +*maxheap.begin() *0.5));
        }
        else
        {
            res.push_back(*maxheap.begin());
        }
        
        //開始移動滑動窗口,並改變大小頂堆內元素
        for(int i = 0; i < nums.size()-k; i++)
        {
            //刪除左窗口元素,因爲已經開始移動
            if(maxheap.find(nums[i]) != maxheap.end())
            {
                maxheap.erase(maxheap.find(nums[i]));
            }
            else if(minheap.find(nums[i]) != minheap.end())
            {
                minheap.erase(minheap.find(nums[i]));
            }
            
            //開始添加一個右窗口元素
            maxheap.insert(nums[i+k]);
            minheap.insert(*maxheap.begin());
            maxheap.erase(maxheap.begin());
            
            while(maxheap.size() < minheap.size())
            {
                maxheap.insert(*minheap.begin());
                minheap.erase(minheap.begin());
            }
            
            if(k%2 == 0)
            {
                res.push_back((*minheap.begin() * 0.5 +*maxheap.begin() *0.5));
            }
            else
            {
                res.push_back(*maxheap.begin());
            }
            
        }
        return res;
        
        
        
    }

private:
    multiset<int> minheap;//升序,begin處最小
    multiset<int,greater<int>> maxheap;//降序,begin()最大

    
};

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章