【LeetCode】【剑指offer】【滑动窗口的最大值(一)】

剑指 Offer 59 - I. 滑动窗口的最大值

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: 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
 

提示:

你可以假设 k 总是有效的,在输入数组 不为空 的情况下,1 ≤ k ≤ nums.length。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 

一、大根堆

对于这道题,可以采用官方的这种方式。就是用一个priority_queue<pair<int,int>>,也就是一个大根堆,但其中的每个元素都存储的是值和索引。这样我们的滑动窗口的时候,每一次都将新的元素入堆的时候,我们想要获取最大的元素,我们从堆顶取出的时候,只需要检查一下它的索引在不在我们的滑动窗口中。不在的话就弹出,在的话就将堆顶的元素值压入我们的容器中。并最终将容器返回。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int size=nums.size();
        vector<int> result;
        //创建一个大根堆,堆中的每一个元素都保存着对应元素的数据和索引
        priority_queue<pair<int,int>> queue;
        for(int i=0;i<k;i++)
        {
            queue.emplace(nums[i],i);
        }
        //将我们当前的最大元素存入我们的结果容器中
        result.push_back(queue.top().first);
        //将我们的滑动窗口右移,然后将新读取到的元素的值和索引存入我们的queue中
        for(int i=k;i<size;i++)
        {
            queue.emplace(nums[i],i);
            //queue.top().second就是读取我们堆顶元素的索引
            //如果这个索引已经不在我们的滑动窗口的范围内了i到i-k+1
            //就将这个数据从堆顶弹出
            while(queue.top().second<i-k+1)
            {
                queue.pop();
            }
            //将不符合要求的数据去除之后,再将我们堆定的元素的值压入我们的容器中
            result.push_back(queue.top().first);
        }
        //将我们的结果返回。
        return result;
    }
};

 

但是上面这种方法虽然能通过测试用例,但是时间开销和空间开销都非常庞大。 

二、双端队列 

然后还有如下双端队列的写法。

双端队列的思想就是:

0123
5473

 如果一个数据的值和索引都比前面的那个数据更大,那么之前那个数据就没有存在的必要了。比方说我们上面的这个数据,假设我们的滑动窗口的大小是2,那么我们一开始滑动窗口中的数据是5和4,最大值是5,但是当7进入我们的滑动窗口的时候,最大值就变成了7.并且在此之后5和4这两个数据都不再会被使用,因为7无论在索引还是数据上都比它们更大。

所以我们可以创建一个双端队列。其中队列的索引是升序排列的,但是队列中索引对应的值是从大到小降序的。每当有新元素从队尾插入队列的时候,就将队尾元素的值跟新元素比较,因为新元素的索引一定是大于老元素的,所以如果新元素的值大于老元素,就可以将队尾的老元素的索引删掉,知道当前队尾存储的索引对应的元素值大于我们当前新元素值为止。

这样我们就能够始终保持队首所存储的索引对应的元素值一定是活动窗口中最大的。

 但是在取出队列的队首元素的时候,我们还需要判断队首元素所存储的索引是不是在我们当前的滑动窗口的范围内。如果不是的话直接弹出,如果是在范围内的话,将其加入我们的结果集中。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
       //获取nums的元素个数
       int size=nums.size();
       //创建一个双端队列q,也就是在队尾和队首都能够插入和删除数据
       deque<int> q;
       //将数据集中的0-k个索引先压入队列中
       //同时我们需要保证该队列中存放的索引是递增的
       //但是其索引对应的值一定是递减的
       for(int i=0;i<k;++i)
       {
           //如果我们新插入的元素比结尾的元素更大,我们就将我们的结尾的元素删掉
           //这里为什么可以删掉呢?
           //因为我们新入队列的元素的下标一定比我们队列中的所有元素都更大
           //那么如果这个元素存在的话,队列中索引比它小,数值比它小的元素都没有存在的意义了
           //所以可以将这些索引永久性地删除。
           while(q.empty()!=true &&nums[i]>=nums[q.back()])
           {
               q.pop_back();
           }
           q.push_back(i);
       }
       //创建一个结果容器
       vector<int> result;
       //将我们当前队首的索引对应的数据也就是我们的最大值放入我们的结果容器中
       result.push_back(nums[q.front()]);
       for(int i=k;i<size;i++)
       {
           //和之前一样的循环删除条件
           while(q.empty()!=true &&nums[i]>=nums[q.back()])
           {
               q.pop_back();
           }
           //将我们新的索引下标添加到我们的队列末尾
           q.push_back(i);
           //如果我们队首的最大元素的索引已经不再我们的滑动窗口的范围中了
           //我们就将其弹出
           while(q.front()<=i-k)
           {
               q.pop_front();
           }
           result.push_back(nums[q.front()]);
       }
       return result;
    }
};

 

但是上面这种方法的时间和空间开销依旧很大 

三、分块+预处理 

或者我们可以采用分块加预处理的方式 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

9

2

3

2

5

7

3

5

4

1

3

4

6

2

 上面是我们的数据,然后我们假定我们的滑动窗口的长度为4

然后创建我们的prefixMax和suffixmax两个容器

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

suffixMax

对于prefixMax来说,我们将从前往后其分为4个一组,然后每四个元素的值就是我们数组中对应的索引的值。

对于suffixmax来说,我们将其从后往前分为4个一组,然后每四个元素的值就是我们数组中的对应的索引的值。

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

5

4

6

suffixMax

2

5

4

2

然后我们prefixmax中其余的元素,假设其下标为i,存储的都是当前分组中当前元素之前的最大值

我们的suffixmax中其余的元素,假设其下标为i,存储的都是当前分组中,当前元素之后的最大值。 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

然后我们从后往前同时遍历prefixmax和suffixmax,以i位置开始的滑动窗口的最大值就是prefixmax中的i加上k-1位置的值。因为该位置的值恰好表示了该位置之前的元素的最大值是9。

而我们从suffixMax对应的i位置可以读取出i所处当前分组中,i所处位置开始的最大值。

我们将这两个值取较大的一个就是我们当前的滑动窗口的最大值。

 

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

ans

9

在获取下标1开始的滑动窗口的时候,我们发现我们的数据在两个不同的分组中,

 

但是我们的prefixMax中的[4]是下一个分组的初始值。我们的 suffixmax中的[1]是第一个分组中,以当前位置开始的最大值,我们同样取这两个数据中的最大值,依旧能满足我们的条件。

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

ans

9

5

下面prefixmax中的7表示的是当前第二个分组中,从5这个位置为结尾最大元素为7,而suffixmax中的3表示的是在第一个分组中从2开始的最大元素为3

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

ans

9

5

7

下面prefixmax中的7表示的是当前第二个分组中,从6这个位置为结尾最大元素为7,而suffixmax中的2表示的是在第一个分组中从3开始的最大元素为2,取最大值7

 

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

ans

9

5

7

7

下面prefixmax中的7表示的是当前第二个分组中,从7这个位置为结尾最大元素为7,而suffixmax中的7表示的是在第二个分组中从4开始的最大元素为2,取最大值7 

 

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

ans

9

5

7

7

7

下面prefixmax中的7表示的是当前第三个分组中,从8这个位置为结尾的最大元素为4,而suffixmax中的7表示的是在第一个分组中从5开始的最大元素为7,取最大值7 

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

ans

9

5

7

7

7

7

下面prefixmax中的4表示的是当前第三个分组中,从9这个位置为结尾的最大元素为4,而suffixmax中的5表示的是在第二个分组中从6开始的最大元素为5,取最大值5

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

ans

9

5

7

7

7

7

5

下面prefixmax中的4表示的是当前第三个分组中,从10这个位置为结尾的最大元素为4,而suffixmax中的5表示的是在第二个分组中从7开始的最大元素为5,取最大值5

 

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

ans

9

5

7

7

7

7

5

5

 下面prefixmax中的4表示的是当前第三个分组中,从11这个位置为结尾的最大元素为4,而suffixmax中的4表示的是在第三个分组中从8开始的最大元素为4,取最大值4

 

 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

ans

9

5

7

7

7

7

5

5

4

 下面prefixmax中的6表示的是当前第四个分组中,从12这个位置为结尾的最大元素为6,而suffixmax中的4表示的是在第三个分组中从9开始的最大元素为4,取最大值6 

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

ans

9

5

7

7

7

7

5

5

4

6

  下面prefixmax中的6表示的是当前第四个分组中,从13这个位置为结尾的最大元素为6,而suffixmax中的4表示的是在第三个分组中从10开始的最大元素为4,取最大值6

 ok我们的表格填写完成

0

1

2

3

4

5

6

7

8

9

10

11

12

13

PrefixMax

9

9

9

9

5

7

7

7

4

4

4

4

6

6

suffixMax

9

3

3

2

7

7

5

5

4

4

4

4

6

2

ans

9

5

7

7

7

7

5

5

4

6

6

0

1

2

3

4

5

6

7

8

9

10

11

12

13

9

2

3

2

5

7

3

5

4

1

3

4

6

2

 对照一下我们上面的数组,确实可行。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        int n = nums.size();
        //创建prefixMax用来记录以当前下标i对应的分组中,以i结尾的前缀最大值
        //创建suffixMax用来记录以当前下标i对应的分组中,以i开始的后缀最大值
        vector<int> prefixMax(n), suffixMax(n);
        for (int i = 0; i < n; ++i) {
            //每经过k个,当前的分组的最大值就是其本身
            if (i % k == 0) {
                prefixMax[i] = nums[i];
            }
            //不是每一个分组开始的元素的话,当前下标i对应的分组,以i结尾的前缀最大值就是前面那个元素(前面那个元素同样也是对应的分组中,以i-1结尾的前缀的最大值)和当前元素的最大值
            else {
                prefixMax[i] = max(prefixMax[i - 1], nums[i]);
            }
        }
        //从后往前记录以当前下标i对应的分组中,以i开始的后缀的最大值
        for (int i = n - 1; i >= 0; --i) {
            //每经过k个(包括第首个元素),当前的分组的最大值就是其本身
            if (i == n - 1 || (i + 1) % k == 0) {
                suffixMax[i] = nums[i];
            }
            //不是每一个分组的开始元素的话,就以当前下标i对应的分组,以i结尾的后缀最大值就是后面那个元素(后面那个元素同样也是对应的分组中,以i+1结尾的后缀最大值)和当前元素的最大值
            else {
                suffixMax[i] = max(suffixMax[i + 1], nums[i]);
            }
        }

        vector<int> ans;
        for (int i = 0; i <= n - k; ++i) {
            //将我们suffixMax[i],也就是以i开始的元素的最大值,和prefixMax[i+k-1]的最大值存入我们的结果容器中
            //也就是我们两个不同的分组中的前半段和后半段的最大值存入结果容器中。
            ans.push_back(max(suffixMax[i], prefixMax[i + k - 1]));
        }
        return ans;
    }
};

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
根据引用\[1\],可以使用暴力解法来求解滑动窗口最大值。具体的做法是,遍历数组,对于每个窗口,使用一个内部循环来找到窗口中的最大值,并将其存储在结果数组中。时间复杂度为O(n*k),其中n为数组长度,k为窗口大小。 根据引用\[2\],还可以使用队列来求解滑动窗口最大值。具体的做法是,使用一个双端队列来维护一个单调递减的窗口。遍历数组,对于每个元素,首先判断队头是否在滑动窗口范围内,如果不在,则将其从队头移除。然后,将当前元素与队尾元素比较,如果当前元素大于队尾元素,则将队尾元素移除,直到队列为空或者当前元素小于等于队尾元素。最后,将当前元素的索引插入队尾。如果滑动窗口的元素个数达到了k个,并且始终维持在窗口中,就将队头元素加入答案数组中。时间复杂度为O(n),其中n为数组长度。 综上所述,可以使用暴力解法或者使用队列来求解leetcode滑动窗口最大值。 #### 引用[.reference_title] - *1* *3* [leetcode239. 滑动窗口最大值](https://blog.csdn.net/kkkkuuga/article/details/124829581)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Leetcode#239. 滑动窗口最大值 (Java解法)](https://blog.csdn.net/paranior/article/details/114890555)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桜キャンドル淵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值