from https://leetcode.com/problems/sliding-window-maximum/ java
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]
.數據結構
直觀的考慮如下幾個問題:app
不直接記錄滑動過程當中的最大值,記錄該最大值的下標, 假設該下標爲j,且值爲x;由於在下一次計算中,須要知道該下標是否在當前窗口裏面;而後讀入下一個元素,假設爲y;那麼如今有三種狀況須要考慮:優化
a. 若是y > x; 那麼y確定是當前窗口的最大值(爲何?);this
b. 若是y < x, 且j + k >= i (當前座標),那麼x就是當前窗口的最大值;spa
c. 若是y < x, 且j + k < i; 也就是說上一個最大值已經移出了當前窗口,這個時候就須要在當前窗口裏面計算最大值;code
那麼根據這個能夠實現如下的代碼, 能夠看到在第三種狀況的時候只是順序查找該窗口裏面的最大值,簡單的分析能夠獲得最壞的狀況下須要O(n * k)的時間;orm
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)的複雜度;
改進後的用自定義的數據結構,且只保存當且窗口內的降序序列:
class Lst<T> { public final int size; private final Object[] array; private int head, tail; Lst(int size) { this.size = size; this.array = new Object[size]; this.head = 0; this.tail = -1; } public T first() { return (T) array[head]; } public T popFirst() { return (T) array[head++]; } public void append(T t) { array[++tail] = t; } public T last() { return (T) array[tail]; } public T popLast() { return (T) array[tail--]; } public boolean isEmpty() { return head > tail; } } public int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; if (n == 0) { return new int[0]; } Lst<Integer> lst = new Lst<>(nums.length + 1); int[] result = new int[n - k + 1]; int index = 0; for (int i = 0; i < k; i++) { if (lst.isEmpty()) { lst.append(i); continue; } int a = nums[i]; while (!lst.isEmpty() && nums[lst.last()] < a) { lst.popLast(); } lst.append(i); } result[index++] = nums[lst.first()]; for (int i = k; i < n; i++) { while (!lst.isEmpty() && lst.first() + k <= i) { lst.popFirst(); } int a = nums[i]; while (!lst.isEmpty() && nums[lst.last()] < a) { lst.popLast(); } lst.append(i); result[index++] = nums[lst.first()]; } return result; }
提交之後用時也在500MS。這個算法的時間複雜度應該是O(n), 由於每一個元素進棧一次,且最多出棧一次。在查找當前窗口的最大值時,是常數時間,不會由於棧的長度變化;