算法:Sliding Window Maximum(滑动窗口最大值)

说明

算法:Sliding Window Maximum
LeetCode地址: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. Return the max sliding window.

Example:

Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3
Output: [3,3,5,5,6,7] 
Explanation: 

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

Note:
You may assume k is always valid, 1 ≤ k ≤ input array’s size for non-empty array.

Follow up:
Could you solve it in linear time?

解题思路1

把数组按照个数为k分组,最后一组可能不是全部数据。以组为单位,从左到右方向,逐个计算组内遇到的最大数,记录为新的数组leftMaxArray;再从右到左方向,逐个计算组内遇到的最大数,记录为新的数组rightMaxArray.
因为从左到右方向–>是最大数,从右到左方向<–也是最大数,所以在某个位置index, k个数里面最多只能跨两个域,所以Max(leftMaxArray[index], rightMaxArray[index - k + 1])就是最大数。

可能文字不清晰,直接拿例子数据说明:

Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3

划分数组分块, size k=3. 最有一块个数可能小于k.
1,3,-1 | -3,5,3  | 6,7 |

--> 从左到右方向, 计算分块里遇到的最大数. 
leftMaxArray[] = 1,3,3 | -3,5,5  | 6,7 

<-- 类似的从右到左计算分块里遇到的最大数.
rightMaxArray[] = 3,3,-1 | 5,5,3  | 7,7 

现在, 滑动窗口在位置i, sliding-max(i) = max{rightMaxArray(i), leftMaxArray(i+w-1)}
sliding_max = 3, 3, 5, 5, 6, 7

执行效率:因为是2n-k+1, 所以是O(n).

代码实现1

public class SlidingWindowMaximum {

    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length == 0 || k <= 0) {
            return new int[0];
        }
        int len = nums.length;
        int[] windowMaxArray = new int[len - k + 1];
        int[] leftMaxArray = new int[len];
        int[] rightMaxArray = new int[len];

        int rightIndex;
        for (int i = 0; i < len; i++) {
            leftMaxArray[i] = i % k == 0 ? nums[i] : Math.max(leftMaxArray[i - 1], nums[i]);
            rightIndex = len - i - 1;
            rightMaxArray[rightIndex] = (i == 0 || (rightIndex + 1) % k == 0) ? nums[rightIndex] : Math.max(rightMaxArray[rightIndex + 1], nums[rightIndex]);
        }

        for (int j = 0; j <= len - k; j++) {
            windowMaxArray[j] = Math.max(leftMaxArray[j + k - 1], rightMaxArray[j]);
        }

        return windowMaxArray;
    }

    public static void main(String[] args) {
        int[] nums = {1,3,-1,-3,5,3,6,7};
        int k = 3;
        SlidingWindowMaximum obj = new SlidingWindowMaximum();
        int[] windowMax = obj.maxSlidingWindow(nums, k);
        System.out.println("Output: " + Arrays.toString(windowMax));
    }



}

运行结果1

Output: [3, 3, 5, 5, 6, 7]

解题思路2

用双向链表Deque来存储遍历过的有效数组下标,这里有两个概念,

  1. Deque存的是索引,数组下标index;
  2. 有效的,也就是最多只能是k个数据,已经小于当前下标i - k + 1, 的数据将要清理出局,这里用 poll() 方法,一直while循环从头开始清理。如果上一个数据小于当前i,那么也就是无效的数据,不可能在i没剔除之前成为最大数,也会被提前清理掉。

那么最大数都在Deque的最前面,所以在for循环的最后一步是存储,最大值。需要注意边界,也就是 i + 1 >= k 开始存结果数据。

nums中的数据最多只会增加一次,清理一次,也就是摊销下来总时间小于2n,时间复杂度为O(n) .

代码实现2

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;

public class SlidingWindowMaximum {

    public int[] maxSlidingWindowWithDeque(int[] nums, int k) {
        if (nums == null || nums.length == 0 || k <= 0) {
            return new int[0];
        }
        int len = nums.length;
        int[] windowMax = new int[len - k + 1];
        int windowIndex = 0;
        // store index
        Deque<Integer> deque = new ArrayDeque<>();
        for (int i = 0; i < len; i++) {
            // remove numbers out of range k
            while (!deque.isEmpty() && deque.peek() < i - k + 1) {
                deque.poll();
            }
            // remove smaller numbers in k range as they are useless
            while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }
            // deque contains index... r contains content
            deque.offer(i);

            if (i + 1 >= k) {
                windowMax[windowIndex++] = nums[deque.peek()];
            }
        }

        return windowMax;
    }

    public static void main(String[] args) {
        int[] nums = {1,3,-1,-3,5,3,6,7};
        int k = 3;
        SlidingWindowMaximum obj = new SlidingWindowMaximum();
        int[] windowMax = obj.maxSlidingWindowWithDeque(nums, k);
        System.out.println("Output: " + Arrays.toString(windowMax));
    }

}

运行结果2

Output: [3, 3, 5, 5, 6, 7]

代码执行效率

Runtime: 11 ms, faster than 73.94% of Java online submissions for Sliding Window Maximum.
Memory Usage: 42.7 MB, less than 41.26% of Java online submissions for Sliding Window Maximum.

总结

Sliding Window Maximum(滑动窗口最大值)解决方案:

  1. 两个方向思维,分块处理。
  2. 用双向链表Deque来顺序存储数据。

代码下载:
https://github.com/zgpeace/awesome-java-leetcode/blob/master/code/LeetCode/src/popular/SlidingWindowMaximum.java

你可能感兴趣的:(算法)