题目:

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

返回滑动窗口中的最大值。

示例

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

初始思路:刚开始没想太多,C++中求数组的最大值的函数:max_element可以解决此问题。果不其然,AC了而且时间复杂度也很高。显然不是这个题目的正常解法,一番苦思冥想后还是打开了题解区。卧槽!哟嘚斯内!下面我就能分析下大佬的方法。

单调队列解法

一个普通队列输入什么数那位置就是什么数,而单调队列在输入一个数时会与前面的比较大小,删除部分元素来保证队列的数是单调递增或递减的。

这样在这个题目中,每次向右移动都会增删一个元素,但我们要在线性时间找到最大值,我们就可以考虑单调队列。

单调队列通常有这几个函数:

  1. class MonotonicQueue {
  2. // 在队尾添加元素 n
  3. void push(int n);
  4. // 返回当前队列中的最大值
  5. int max();
  6. // 队头元素如果是 n,删除它
  7. void pop(int n);
  8. }

然后我们假设已经有了单调队列这个数据结构来满足我们的要求,我们可以把此题的解题框架搭出来:

  1. vector<int> maxSlidingWindow(vector<int>& nums, int k) {
  2. MonotonicQueue window;
  3. vector<int> res;
  4. for (int i = 0; i < nums.size(); i++) {
  5. if (i < k - 1) { //先把窗口的前 k - 1 填满
  6. window.push(nums[i]);
  7. } else { // 窗口开始向前滑动
  8. window.push(nums[i]);
  9. res.push_back(window.max());
  10. window.pop(nums[i - k + 1]);
  11. // nums[i - k + 1] 就是窗口最后的元素
  12. }
  13. }
  14. return res;
  15. }

框架实现还是很简单的,现在我们主要是怎么实现单调队列的那些函数:

实现单调队列:

在此之前我们先认识另一种数据结构:deque,即双端队列:

  1. class deque {
  2. // 在队头插入元素 n
  3. void push_front(int n);
  4. // 在队尾插入元素 n
  5. void push_back(int n);
  6. // 在队头删除元素
  7. void pop_front();
  8. // 在队尾删除元素
  9. void pop_back();
  10. // 返回队头元素
  11. int front();
  12. // 返回队尾元素
  13. int back();
  14. }

这些操作的复杂度都是O(1)。我们可以利用这个数据结构来实现单调队列。

前面我们已经提到过单调队列就是队尾每增一个元素时,与前面的进行比较,把比它小的元素删掉:

  1. void push(int n) {
  2. while (!data.empty() && data.back() < n)
  3. data.pop_back();
  4. data.push_back(n);
  5. }

这样就保证了队列从头到尾是单调递减的。

由此我们知道当前最大的元素肯定是在队头,所以max()函数实现:

  1. int max() {
  2. return data.front();
  3. }

最后当窗口向右移动一步时,之前最左端的数需要pop掉,此时队列里的数都是单调递减的。最左端的数有两种情况:

  1. 1.push时,因为比push的数小,所以已经被pop
  2. 2.若没被pop,说明他一直比后面push的数大,所以在队头位置。

所以pop函数实现:

  1. void pop(int n) {
  2. if (!data.empty() && data.front() == n)
  3. data.pop_front();
  4. }

自此我们已将单调队列构造出来,接下来就可以代入上面的框架来最后完善我们的代码:

  1. class MonotonicQueue {
  2. private:
  3. deque<int> data;
  4. public:
  5. void push(int n) {
  6. while (!data.empty() && data.back() < n)
  7. data.pop_back();
  8. data.push_back(n);
  9. }
  10. int max() { return data.front(); }
  11. void pop(int n) {
  12. if (!data.empty() && data.front() == n)
  13. data.pop_front();
  14. }
  15. };
  16. vector<int> maxSlidingWindow(vector<int>& nums, int k) {
  17. MonotonicQueue window;
  18. vector<int> res;
  19. for (int i = 0; i < nums.size(); i++) {
  20. if (i < k - 1) { //先填满窗口的前 k - 1
  21. window.push(nums[i]);
  22. } else { // 窗口向前滑动
  23. window.push(nums[i]);
  24. res.push_back(window.max());
  25. window.pop(nums[i - k + 1]);
  26. }
  27. }
  28. return res;
  29. }

三、复杂度分析:

乍一看push中含有while循环,时间复杂度不是O(1)啊,所以本算法时间复杂度也不是线性时间吧?

单独看push操作确实不是O(1),但是整个算法的复杂度依然是O(N)。要这样想,nums中每个元素最多被push_back和pop_back一次,没有多余操作,所以整体复杂度还是O(N);

空间复杂度就很简单了,就是窗口的大小O(k)。

四、最后总结:

有些朋友会觉得「单调队列」和「优先级队列」比较像,实际上差别很大的。

单调队列在添加元素的时候靠删除元素保持队列的单调性,相当于抽取出某个函数中单调递增(或递减)的部分;而优先级队列(二叉堆)相当于自动排序,差别大了去了。

本文参考了labuladong的解法

LeetCode(239.滑动窗口的最大值的更多相关文章

  1. Java实现 LeetCode 239 滑动窗口最大值

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

  2. 【leetcode 239. 滑动窗口最大值】解题报告

    思路:滑动窗口的思想,只要是求连续子序列或者子串问题,都可用滑动窗口的思想 方法一: vector<int> maxSlidingWindow(vector<int>& ...

  3. Leetcode 239.滑动窗口最大值

    滑动窗口最大值 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧.你只可以看到在滑动窗口 k 内的数字.滑动窗口每次只向右移动一位. 返回滑动窗口最大值. 示例: ...

  4. leetcode 239. 滑动窗口最大值(python)

    1. 题目描述 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧.你只可以看到在滑动窗口内的 k 个数字.滑动窗口每次只向右移动一位. 返回滑动窗口中的最大值. 示 ...

  5. 代码随想录第十三天 | 150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素

    第一题150. 逆波兰表达式求值 根据 逆波兰表示法,求表达式的值. 有效的算符包括 +.-.*./ .每个运算对象可以是整数,也可以是另一个逆波兰表达式. 注意 两个整数之间的除法只保留整数部分. ...

  6. leetcode全部滑动窗口题目总结C++写法(完结)

    3. 无重复字符的最长子串 A: 要找最长的无重复子串,所以用一个map保存出现过的字符,并且维持一个窗口,用le和ri指针标识.ri为当前要遍历的字符,如果ri字符在map中出现过,那么将le字符从 ...

  7. 剑指0ffer59.滑动窗口的最大值

    给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值. 示例: 输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3输出: [3,3,5,5,6,7] ...

  8. lintcode 滑动窗口的最大值(双端队列)

    题目链接:http://www.lintcode.com/zh-cn/problem/sliding-window-maximum/# 滑动窗口的最大值 给出一个可能包含重复的整数数组,和一个大小为  ...

  9. 剑指offer:滑动窗口的最大值

    滑动窗口的最大值 题目描述 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值.例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值 ...

随机推荐

  1. php配置memcached的扩展。

    (一)安装memcached服务器 1根据系统下载相应版本的memcached服务器版本:如win7(64位=====>memcached-win64/memcached.exe 2.解压到目录 ...

  2. Archives: 2018/11

    There are 35 posts in total till now. 11月 11, 2018 HTTP 11月 11, 2018 TCP与UDP 11月 10, 2018 Python测试 1 ...

  3. 微软研究员Eric Horvitz解读 “人工智能百年研究”

    本文翻译自ScienceInsider"A 100-year study of artificial intelligence? Microsoft Research's Eric Horv ...

  4. 林轩田机器学习基石笔记1—The Learning Problem

    机器学习分为四步: When Can Machine Learn? Why Can Machine Learn? How Can Machine Learn? How Can Machine Lear ...

  5. JavaScript的数据类型有哪些?

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  6. 别恐慌,大众关心的人工智能问题学界都在努力求解——我眼中的AAAI 2015大会

    2015大会" title="别恐慌,大众关心的人工智能问题学界都在努力求解--我眼中的AAAI 2015大会"> 作者:微软亚洲研究院副研究员 黄铂钧 今年是美国 ...

  7. python 写个冒泡排序吧

    冒泡排序 介绍: 冒泡排序(Bubble Sort,台湾译为:泡沫排序或气泡排序)是一种简单的排序算法.它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数列的工作 ...

  8. Ubuntu上搭建GPU服务器

    1.安装显卡驱动 2.安装CUDA 3.安装cuDNN 下载: 根据显卡类型以及操作系统,选定CUDA版本和语言设置,下载对应的显卡驱动. 驱动下载地址 安装 $ sudo ./NVIDIA-Linu ...

  9. Git忽略规则(.gitignore配置)不生效原因和解决

    问题: .gitignore中已经标明忽略的文件目录下的文件,git push的时候还会出现在push的目录中,或者用git status查看状态,想要忽略的文件还是显示被追踪状态. 原因是因为在gi ...

  10. centos7上Jenkins通过rpm包方式直接安装及使用war包方式升级

    一.通过rpm包方式直接安装jenkins 1.官网下载rpm安装包(前提是安装jdk) wget https://pkg.jenkins.io/redhat-stable/jenkins-2.121 ...