【代码随想录】Day 13 (滑动窗口最大值、TopK)

第一题

力扣

 从双重for循环的暴力解法超出时间限制。

 学习记录:

创建一个单调递减队列,因为要能从前后都可以pop,所以用deque双向队列。

单调递减队列的逻辑:

当要压入元素时,需要一个一个对比之前队尾元素的大小,如果队尾元素小于要压入的值,直接pop掉,然后将要压入的元素直接放到单调递减队列的对应位置中;

class Solution {
private:
    class MyQueue { //创建单调递减队列
    public:
        deque<int> que;
        void push(int val) {
            while (!que.empty() && val > que.back()) { //如果队列尾部的元素小于要压入的元素,则全部弹出以后再压入,保证单调递减
                que.pop_back();
            }
            que.push_back(val);
        }

        void pop(int val) {
            if (!que.empty() && val == que.front()) { //当要弹出的值为队头元素时才弹出,否则不管(因为在push的时候已经保证了单调递减,队头为当前滑动窗口最大值,最大值之前的元素本身就没有push进来)
                que.pop_front();
            }
        }

        int getfront() {
            return que.front();
        }
    };

public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> max_list;
        MyQueue myque;        
        for (int i=0; i < k; i++) { //第一个窗口,循环结束后队列里为最大值及后面小于它的值(单调递减)
            myque.push(nums[i]);
        }
        max_list.push_back(myque.getfront()); //第一个窗口的最大值
        for (int i=k; i<nums.size(); i++) { //从第二个窗口开始直到结束,i从k开始是因为队列是从尾部增加元素的,因此要从第一个窗口结束的后一位开始入队
            myque.pop(nums[i-k]); //弹出上一个窗口的起始值,以便维护窗口大小
            myque.push(nums[i]);
            max_list.push_back(myque.getfront());
        }
        return max_list;
    }
};

 复习,双端队列做:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if (k == 1) return nums;
        vector<int> res;
        deque<int> dq;
        for (int i = 0; i < nums.size(); i++) {
            while (!dq.empty() && dq.front() <= i-k) { //移除所有不在窗口里的索引
                dq.pop_front();
            }
            while (!dq.empty() && nums[dq.back()] < nums[i]) { //保持队列对应元素单调递减,因为既在窗口里又小于nums[i]的元素已经不可能是maxnum了,所以可以直接删掉它们
                dq.pop_back();
            }
            dq.push_back(i);
            if (i >= k-1)  res.push_back(nums[dq.front()]);//以窗口右边界为一次观察
        }
        return res;
    }
};

 第二题

力扣

想到的是用map存次数,然后排序再输出前k高的元素,由于map没有sort,所以又新建了一个vector<pair>存了map的键值对,然后对vector进行排序的。调了很久,已AC:

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        std::map<int,int> count;
        for (int i : nums) {
            count[i]++;
        }
        vector<std::pair<int,int>> value;
        for (auto it = count.begin(); it != count.end(); it++) {
            value.push_back(make_pair(it->second, it->first));
            // cout<<value.back().first<<","<<value.back().second<<endl;
        } 
        sort(value.begin(),value.end());
        for (auto it = value.begin(); it!= value.end();it++) {
            // cout<<it->first<<","<<it->second<<endl;
        }
        vector<int> result;
        for (int i = 1; i <= k; i++) { 
            result.push_back(value.at(value.size()-i).second);
            // cout<<result.back()<<endl;
        }
        return result;
    }
};

学习记录:

因为是求频率前k高的元素,只需要维护k个有序集合就可以了,没必要全部排序。

大顶堆、小顶堆:特别擅长在大数据集中求前k个高频或者低频的结果;维护k个节点的堆里数值比较大或者比较小的元素,如果求高频、用大顶堆,那么数值最大的根节点就被pop出去了,留下的是数值小的元素;因此求高频用小顶堆,低频用大顶堆

priority_queue 优先级队列的底层实现是,底层存储数据的容器是vector,其也是容器适配器。可以把它当成堆。默认情况下priority_queue是大堆。

在创建小顶堆时,小顶堆的元素应该是pair<int, int>类型,将map的键值对都存入小顶堆中。pair类型的小顶堆默认是以first来比较的,因此可以使用默认函数greater定义小顶堆,然后将map的键和值调换一下作为小顶堆的pair。以下是代码:

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
       std::map<int,int> count;
       for (int i:nums) {
           count[i]++;
       }
       priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pri_que;
       for (auto it = count.begin(); it != count.end(); it++) {
           pri_que.push(make_pair(it->second,it->first));
           if (pri_que.size() > k) {
               pri_que.pop();
           }
       }
       vector<int> result;
       for (int i = k-1; i >= 0; i--) {
           result.push_back(pri_que.top().second);
           pri_que.pop();
       }
       return result;
    }
};

以下为代码随想录自定义比较类mycomparison的写法,他是将map键值对直接存到小顶堆里,定义小顶堆时用的是自定义比较类,也就是比较pair.second的大小来排序(默认的less创建大顶堆,重载的“()”是左边小于右边):

class Solution {
public:
    // 小顶堆
    class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 要统计元素出现频率
        unordered_map<int, int> map; // map<nums[i],对应出现的次数>
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 对频率排序
        // 定义一个小顶堆,大小为k
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

        // 用固定大小为k的小顶堆,扫面所有频率的数值
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                pri_que.pop();
            }
        }

        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;

    }
};

priority_queue类的模板参数列表,里面有三个参数:class T,class Container = vector<T>,class Compare = less<typename Container::value_type> 

template <class T, class Container = vector<T>,

                              class Compare = less<typename Container::value_type> >

class priority_queue;

  • class T:T是优先队列中存储的元素的类型。
  • class Container = vector<T>:Container是优先队列底层使用的存储结构,可以看出来,默认采用vector。
  • class Compare = less<typename Container::value_type> :Compare是定义优先队列中元素的比较方式的类。

自定义类的比较方式是需要用户自己给出的,并且需要手动填充到Compare 参数的位置。Compare后面跟着的less<typename Container::value_type>就是默认的比较类,默认是按小于(less)的方式比较,这种比较方式创建出来的就是大堆。所以优先队列默认就是大堆。

如果需要创建小堆,就需要将less改为greater。

less类的函数参数。

template <class T> 
        struct less : binary_function <T,T,bool> {
          bool operator() (const T& x, const T& y) const {return x<y;}
};

        上面是less类的内部函数,less类的内部重载(),这也就是前面提到的仿函数,参数列表中有左右两个参数,左边小于右边的时候返回true,此时优先队列就是大堆。

template <class T> 
        struct greater : binary_function <T,T,bool> {
          bool operator() (const T& x, const T& y) const {return x>y;}
}; 

        上面是greater类的内部函数,greater类的内部重载(),这也就是前面提到的仿函数,参数列表中有左右两个参数,左边大于右边的时候返回true,此时优先队列就是小堆。

注意:less类和greater类只能比较内置类型的数据的大小,如果用户需要比较自定义类型的数据,就需要自己定义一个比较类,并且重载()

        同时less类和greater类也具有模板参数,因为他们也是模板,所以我们如果要存储自定义类型的元素,就要将自定义类型作为模板参数传递给less类和greater类。参考下列定义,存储的元素类型为pair<int,int>,就需要将greater后面的参数也写上pair<int,int>:

priority_queue< pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> pri_que;

具体参考博客:C++ 优先队列 priority_queue 使用篇_爱喝酸奶!的博客-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值