力扣 239. 滑动窗口最大值 优先队列+单调队列

题目

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

示例 2:

输入:nums = [1], k = 1
输出:[1]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

法1.优先队列

优先队列介绍

可以查看这个连接

让我们通过一个简单的示例了解优先队列。

在上图中,我们通过使用push()函数插入了元素,并且插入操作与普通队列相同。但是,当我们使用pop()函数从队列中删除元素时,优先级最高的元素将首先被删除。

优先队列中的元素有优先级,优先级高的元素先删除(大顶堆)

priority_queue <int> q;//默认升序队列

priority_queue<int,vector<int>,less<int> > q; //另一种构建大顶堆的方法 

priority_queue <int,vector<int>,geater<int> >q;//降序队列

主要操作

函数 描述
push() 它将新元素插入优先队列。
pop() 它将优先级最高的元素从队列中删除。
top() 此函数用于寻址优先队列的最顶层元素。
size() 返回优先队列的大小。
empty() 它验证队列是否为空。基于验证,它返回队列的状态。
swap() 它将优先队列的元素与具有相同类型和大小的另一个队列交换。
emplace() 它在优先队列的顶部插入一个新元素。

 题解

 来自官方

主要思想是利用优先队列的大顶堆,队列的top一定是值最大的,只需要按顺序放入元素就可以依次获取到最大值。

同时有一个问题,如5,1,1,...,第一个元素5很大,如果不处理那么后面获取的最大值会变成5

所以对最大值进行判断,是否在窗口内,如果不在窗口内就删除,为了判断在放入队列时,只放入元素不够,可以同时放入元素下标。窗口右端为当前遍历到的下标i,则左侧为i-k

那么队列的构造为priority_queue<pair<int,int>> q;,放入的值为q.emplace(nums[i],i);

每次放入元素后就进行检查最大值下标是否已经出界,在while循环中判断q.top().second(即最大值下标)是否在窗口内,如果出界也只会在窗口左侧,

所以判断seconde<=i-k,i是当前遍历元素下标,i-k为窗口左端

代码
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        int len=nums.size();
        //定义优先队列q
        priority_queue<pair<int,int>> q;
        //先放入前k个元素
        for(int i=0;i<k;i++){
            q.emplace(nums[i],i);
        }
        //获得前k个元素最大值
        res.push_back(q.top().first);
        //依次滑动窗口
        for(int i=k;i<len;i++){
            q.emplace(nums[i],i);//放入元素
            //判断最大值是否在窗口外
            while(q.top().second<=i-k){
                q.pop();//移除出界最大值
            }
            res.push_back(q.top().first);//获得当前窗口最大值
        }
        return res;

    }
};

法2.单调队列

来自官方

思路是使用一个双端队列(首尾都可以插入、删除元素)q维护一组元素值递减的下标,同时下标超出窗口的会进行清理。

这样每次取q.front就可以获取当前窗口最大值,举个例子:1,2,1,4,1,1,1,k=3

1.遍历前K个元素,即初始窗口,判断当前元素nums[i]和队列q的尾端元素nums[q.back()],如果当前元素比尾端大,就清理尾端元素(采用while);否则q就加入当前下标i

q:push 1=>pop 1(因为2>1)=>push2=>push 1(因为1<2)   得到[2,1],q.front就是当前窗口最大值

2.滑动窗口,从k开始遍历,和1.一样判断大小进行清理。同时增加下标是否超出窗口范围的判断,如果超出就清理掉。

分段讲解,先遍历到i=3时,值为4,窗口[2,1,4]:

q:[2,1]=>pop 1(4>1)=>pop 2(4>2)=>push 4   得到[4],取front为4,即[2,1,4]中最大值4;

当窗口为[4,1,1]时q.front仍是4,可窗口为[1,1,1]时,对4的下标进行判断,发现出界就进行清理。

这样维护得到的q在遍历时取front就可以得到当前窗口最大值。

代码

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        int len=nums.size();
        deque<int> q;//存放未被删除的下标
        //先放入前k个
        for(int i=0;i<k;i++){
            //如果当前元素比尾端元素大,就pop尾端
            while(!q.empty()&&nums[i]>nums[q.back()]){
                q.pop_back();
            }//然后添加当前元素下标,
            q.push_back(i);
        }
        //第一个窗口的最大值
        res.push_back(nums[q.front()]);
        //滑动窗口
        for(int i=k;i<len;i++){
            //同上
            while(!q.empty()&&nums[i]>nums[q.back()]){
                q.pop_back();
            }
            q.push_back(i);
            //加入元素下标是否出窗口的判断
            while(q.front()<=i-k){
                q.pop_front();
            }
            //此窗口最大值
            res.push_back(nums[q.front()]);
        }
        return res;
    }
};
posted @ 2022-05-28 16:57  付玬熙  阅读(96)  评论(0编辑  收藏  举报