import java.util.*;

public class Solution {
    /**
     * 本题的解题思路很值得学习
     * 简单的方法就是每个滑动窗口我们都遍历一遍来寻找最大值
     * 可是这样子的时间复杂度就为 O(len*size) 时间复杂度太高了,因此我们就只能能改进方法
     * 改进后的方法:因为我们只需要知道每个滑动窗口中的最大值,因此同一个滑动窗口中的比最大值
     * 小的值均没有意义,即我们不需要他们,因此我们这里通过一个双端队列(看答案学的,这里的
     * 思想很重要)来维护,双端队列中存的是元素对应在 num 数组中的下标(这个思想也很重要,
     * 存下标正好方便我们判断当前元素是否还在滑动窗口内,若存入值,就不好判断),具体的维护
     * 方法就是每次我们滑动窗口变化的时候,先判断队列头部元素是否在当前窗口内,不在就 poll
     * 掉,之后我们在新元素入队列时要"挤"掉前面小于它的数(这些数就属于小于最大值的数,我们不
     * 需要他们),这样子队列头就一直是当前滑动窗口中的最大值.之后每组就遵守相同的规则,同时将
     * 队列头部元素入 res 即可.
     * 改进后的时间复杂度:O(len)
     * @param num
     * @param size
     * @return
     */
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        ArrayList<Integer> res = new ArrayList<>();
        //排除不合法的条件
        if (size <= 0 || size > num.length) {
            return res;
        }
        //辅助双端队列 (其中存入的是下标)
        Deque<Integer> deque = new ArrayDeque<>();
        //先将前 size 个元素入双端队列,且每个元素进入的时候都"挤"掉前面小于它的数
        for (int i = 0; i < size; i++) {
            while (!deque.isEmpty() && num[deque.peekLast()] < num[i]) {
                deque.pollLast();
            }
            deque.offerLast(i);
        }
        //先将这一组的最大值放入 res 中
        res.add(num[deque.peekFirst()]);
        //处理后续
        for (int i = size; i < num.length; i++) {
            //先判断双端队列中头部元素是否当前滑动窗口内,不在就弹出
            if (deque.peekFirst() < i - size + 1) {
                deque.pollFirst();
            }
            //将当前窗口要加入的值入双端队列,规则与之前相同
            while (!deque.isEmpty() && num[deque.peekLast()] < num[i]) {
                deque.pollLast();
            }
            deque.offerLast(i);
            //将当前滑动窗口的最大值加入 res 中
            res.add(num[deque.peekFirst()]);
        }
        return res;
    }
}