Day11:239. 滑动窗口最大值、347.前 K 个高频元素


239. 滑动窗口最大值

题目链接

思路

  1. 暴力方法
    遍历一遍的过程中每次从窗口中再找到最大的数值,这样很明显是O(n × k)的算法
  2. 单调栈
    • 用大顶堆(优先级队列)来存放这个窗口里的k个数字,这样就可以知道最大的最大值是多少了,但是问题是这个窗口是移动的,而大顶堆每次只能弹出最大值,我们无法移除其他数值,这样就造成大顶堆维护的不是滑动窗口里面的数值了。
    • 用小顶堆存放,这样每次弹出的元素一定不是最大值

其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。C++中没有直接支持单调队列,需要我们自己来实现一个单调队列。不要以为实现的单调队列就是 对窗口里面的数进行排序,如果排序的话,那和优先级队列又有什么区别了呢。

代码实现

1.deque容器实现单调队列
常用的queue在没有指定容器的情况下,deque就是默认底层容器。单调栈在不同的情境下的书写规则是不一样的,最好是做到理解底层逻辑。

2. 实现函数
先把滑动窗口最大长度元素放进来,得到que.front( ),再开始一个一个历遍,只需要保存值得维护的有可能的最大元素,当value!=que.front( )时,就先保存front,因为它还没有历遍到需要弹出的位置。以此类推。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> que;
        vector<int> result;
        for (int i = 0; i < k; i++) {
            while (!que.empty() && nums[i]> que.back()) {
                que.pop_back();
            }
            que.push_back(nums[i]);//先找出前k个元素组成的单调队列
        }
        result.push_back(que.front()); 
        for (int i = k; i < nums.size(); i++) {
            if (!que.empty()&& nums[i - k]==que.front()) {
                que.pop_front();
            }//当保存的队列头部最大元素应该离开窗口了,就把它删除
            while (!que.empty() && nums[i]> que.back()) {
                que.pop_back();
            }//继续维护单调队列
            que.push_back(nums[i]);//取最大元素,即队头元素
            result.push_back(que.front()); // 记录对应的最大值
        }
        return result;
    }
};

时间复杂度: O(n)
空间复杂度: O(k)

总结

维护一个固定大小窗口的最大元素这个方法还是很实用的,需要更好的掌握。二刷之后发现自己落下的还是太多了。


347.前 K 个高频元素

题目链接

思路

  1. 要统计元素出现频率
    用map最合适,key记录元素类型,value记录元素出现次数

  2. 对频率排序
    用优先队列就可以排好序。但优先队列分为大顶堆和小顶堆。
    -第一反应题目要求前 K 个高频元素,那么果断用大顶堆。定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,无法保留下来前K个高频元素。
    -所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

  3. 找出前K个高频元素
    寻找步骤如下:
    在这里插入图片描述

代码实现

class Solution {
public:
    // 小顶堆
  	class mycomparison{
  	public:
  		bool opretor()(const pair<int,int>& left,const pair<int,int>& right){
  			return left.second>right.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;

    }
};

时间复杂度: O(nlogk)
空间复杂度: O(n)

总结

  1. 写优先队列的cmp函数的时候,return left>right 就是从大到小,return left<right 就是从小到大(刚好反过来),cmp函数写在类或者结构体里面
  2. priority_queue的定义中第一位为数据类型,第二位为储存容器,第三位为排序方法
  3. 当要用下标访问vector时需要定义容器大小 //vector result(k)
  4. pair的key值为first,value值为second
  5. map需应用unordered_map更方便

碎碎叨

今天题目有点难,好多不会的东西呜呜呜。加油加油加油!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值