Leetcode 239. 滑动窗口最大值(单调队列解法)

  • 题目:

    • 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
    • 返回 滑动窗口中的最大值 。
    • 1 <= nums.length <= 10^5
    • -10^4 <= nums[i] <= 10^4
    • 1 <= k <= nums.length
  • 解法:

    • 考虑单调双端队列(存元素与下标)
    • 保证队头到队尾元素递减,先将 k - 1 个元素以及下标依次按规则放入队尾,
    • 接着依次将后续元素按相同规则放入队尾,放一个后就取出队头元素(不删),
      • 如果该元素下标不在当前滑动窗口内,则删除继续取队头元素,直到元素在窗口内,
    • 此时它就是当前窗口最大元素(不删)
    • 循环上述操作直到最后一个元素入队尾、查到窗口内队头结束。
    • 放入规则是:
      • 如果当前队尾有元素且不大于自己的,则删除队尾元素,直到空或者元素比自己大就放到队尾即可。
    • 解释:
      • 每次放入队尾时,删除不大于自己的元素不会影响后续取查询,因此后续取窗口中只要有删除的元素就一定包含自己,
      • 其次第 k 个元素以及之后的元素每次是一定会进入队尾的,接着查询队头元素删除时最多就是将插入队尾前的所有元素删除(均不在窗口),那么一定不会变空
    • 时间复杂度:O(n)(每个元素最多进出一次),空间复杂度:O(n)

代码:

    /**
     * 考虑单调双端队列(存元素与下标)
     * 保证队头到队尾元素递减,先将 k - 1 个元素以及下标依次按规则放入队尾,
     * 接着依次将后续元素按相同规则放入队尾,放一个后就取出队头元素(不删),
     * 如果该元素下标不在当前滑动窗口内,则删除继续取队头元素,直到元素在窗口内,
     * 此时它就是当前窗口最大元素(不删)
     * 循环上述操作直到最后一个元素入队尾、查到窗口内队头结束。
     * 放入规则是:
     * 如果当前队尾有元素且不大于自己的,则删除队尾元素,直到空或者元素比自己大就放到队尾即可。
     * 解释:
     * 每次放入队尾时,删除不大于自己的元素不会影响后续取查询,因此后续取窗口中只要有删除的元素就一定包含自己,
     * 其次第 k 个元素以及之后的元素每次是一定会进入队尾的,接着查询队头元素删除时最多就是将插入队尾前的所有元素删除(均不在窗口),那么一定不会变空
     *
     * 时间复杂度:O(n)(每个元素最多进出一次),空间复杂度:O(n)
     */
    public int[] maxSlidingWindowOptimizeSecond(int[] nums, int k) {
        // 判空
        if (nums == null || nums.length < k || k <= 0) {
            return new int[0];
        }

        int numsLen = nums.length;
        int[] res = new int[numsLen - k + 1];

        // 单调双端队列,队头到队尾元素递减(保证不扩容)
        Deque<List<Integer>> deque = new ArrayDeque<>(numsLen + 1);


        // 先按规则放 k - 1 个元素到队尾,然后再放一个元素就取一个元素,循环找到当前窗口的队头
        for (int i = 0; i < numsLen; i++) {
            if (i < k - 1) {
                addPollLastDeque(deque, nums[i], i);
            } else {
                addPollLastDeque(deque, nums[i], i);

                res[i - k + 1] = peekAndPollFirstDeque(deque, nums[i], i - k + 1);
            }
            deque.forEach(e -> System.out.print(e.get(0) + ":" + e.get(1) + " "));
            System.out.println();
        }

        return res;
    }

    /**
     * 如果当前队尾有元素且不大于自己的,则删除队尾元素,直到空或者元素比自己大就放到队尾即可。
     */
    private void addPollLastDeque(Deque<List<Integer>> deque, int num, int index) {
        while (deque.peekFirst() != null && deque.peekLast().get(0) <= num) {
            deque.pollLast();
        }
        List<Integer> numList = new ArrayList<>();
        numList.add(num);
        numList.add(index);
        deque.addLast(numList);
    }

    /**
     * 取出队头,如果该元素下标不在当前滑动窗口内,则删除继续取队头元素,直到元素在窗口内,
     * @param begin 滑动窗口开始位置(含)
     */
    private int peekAndPollFirstDeque(Deque<List<Integer>> deque, int num, int begin) {
        while (deque.peekFirst() != null && deque.peekFirst().get(1) < begin) {
            deque.pollFirst();
        }

        return deque.peekFirst().get(0);
    }

你可能感兴趣的:(Leetcode 239. 滑动窗口最大值(单调队列解法))