标签:
from https://leetcode.com/problems/sliding-window-maximum/
Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
For example,
Given nums = [1,3,-1,-3,5,3,6,7]
, and k = 3.
Window position Max --------------- ----- [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
Therefore, return the max sliding window as [3,3,5,5,6,7]
.
直观的考虑以下几个问题:
不直接记录滑动过程中的最大值,记录该最大值的下标, 假设该下标为j,且值为x;因为在下一次计算中,需要知道该下标是否在当前窗口里面;然后读入下一个元素,假设为y;那么现在有三种情况需要考虑:
a. 如果y > x; 那么y肯定是当前窗口的最大值(为什么?);
b. 如果y < x, 且j + k >= i (当前坐标),那么x就是当前窗口的最大值;
c. 如果y < x, 且j + k < i; 也就是说上一个最大值已经移出了当前窗口,这个时候就需要在当前窗口里面计算最大值;
那么根据这个可以实现以下的代码, 可以看到在第三种情况的时候只是顺序查找该窗口里面的最大值,简单的分析可以得到最坏的情况下需要O(n * k)的时间;
public int[] maxSlidingWindow1(int[] nums, int k) { int n = nums.length; if (n == 0) { return new int[0]; } int[] result = new int[n - k + 1]; int index = 0; for (int i = 1; i < k; i++) { if (nums[result[index]] < nums[i]) { result[index] = i; } } for (int i = k; i < n; i++) { int j = result[index++]; int a = nums[j]; int b = nums[i]; if (b >= a) { result[index] = i; } else { if (i - j < k) { result[index] = j; } else { result[index] = j + 1; for (int m = j + 2; m <= i; m++) { if (nums[result[index]] < nums[m]) { result[index] = m; } } } } } for (int i = 0; i < result.length; i++) { result[i] = nums[result[i]]; } return result; }
当然可以有改进的地方;考虑这样一个输入(部分)5, 3, 1, 2, 1, 窗口大小为3, (结果为5, 3, 2); 然后从1开始模拟上面的算法:
a. 读入1, 前一个最大值为5,且在窗口里面,所以当前窗口最大值为 5;
b. 读入2, 最大值5移出了窗口,所以要从#1开始计算最大值,并得到3;
c. 读入1, 最大值3移出了窗口,所以要从#2开始重新计算,并得到2;
那么有没有办法可以不用对重复的处理已经被处理的输入呢?答案自然是有。考虑使用一个stack,它保存一个降序(坐标)的数列。处理到某一个位置的时候,因为stack里面处于下面的值(如果存在)肯定比当前值大;那么只要在stack里面找到最下面的,且处于当前窗口内的值,就是当前窗口的最大值;
代码如下所示:
public int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; if (n == 0) { return new int[0]; } int[] result = new int[n - k + 1]; Stack<Integer> stack = new Stack<>(), temp = new Stack<>(); result[0] = nums[0]; stack.push(0); for (int i = 1; i < k; i++) { if (result[0] < nums[i]) { result[0] = nums[i]; } int a = nums[i]; while (!stack.isEmpty() && nums[stack.peek()] < a) { stack.pop(); } stack.push(i); } int index = 1; for (int i = k; i < n; i++) { int a = nums[i]; while (!stack.isEmpty() && nums[stack.peek()] < a) { stack.pop(); } if (stack.isEmpty()) { result[index++] = a; } else { if (stack.peek() + k <= i) { result[index++] = a; } else { while (!stack.empty() && stack.peek() + k > i) { temp.push(stack.pop()); } result[index++] = nums[temp.peek()]; while (!temp.empty()) { stack.push(temp.pop()); } } } stack.push(i); } return result; }
简单分析一下:还是以5, 3, 1, 2, 1为例;
a. 当处理到1的时候,stack 里面为5, 3 (应该为坐标,为了看得清楚,直接用值替代);并且5在窗口里面,所以当前窗口最大值为5;stack 变为5, 3, 1;
b. 处理2的时候,比2小的1会被pop掉,stack变为5, 3, 2; 然后3是当前窗口里面最下面的值,所以最大值是3;
c. 处理最后一个1的时候, stack里面当前窗口最大的为2, 所以最大值是2;前面的算法在这里是需要从1开始处理的,这里只需要处理到2,就可以了。
performance
但是提交了以后,算法1的时间为500MS,算法2为700MS;sign~~~~
简单的分析一下,算法1只使用了数组,而算法2使用了stack;而且算法2使用了两个stack,用于查找当前窗口里面最大值,事实上如果使用自己定义个数据结构,是可以只用一个;应该会有提升;算法2的时间复杂度,假设使用一个stack,那么每个元素被入栈一次,最多出栈一次,最坏情况比如一个降序的序列,那么不幸的是,时间复杂度任然是O(n * k);
还有一个优化是,stack只保留上一个窗口的最大值和当前窗口的降序序列,查找当前窗口最大的元素可以在常数时间内完成,可以达到O(n)的复杂度;
标签:
原文地址:http://my.oschina.net/u/922297/blog/480086