剑指Offer-66-滑动窗口的最大值

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dawn_after_dark/article/details/84075370

项目地址:https://github.com/SpecialYy/Sword-Means-Offer

问题

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

解析

滑动窗口其实就是给定区间求出最大值而已,只不过这个窗口会不断向右滑动,现让你高效的求出每次滑动后当前窗口内的最大值。

思路一

老方法,暴力解决,这是不得已的情况下最坏的打算。就算是最低级代码,请你漂亮的且无bug的写出来。思路很简单,实现一个子函数用于遍历求出给定区间内最大值。主函数只要循环给出区间即可。

	/**
     * 普通做法,不断分段求最大值
     * @param num
     * @param size
     * @return
     */
    public ArrayList<Integer> maxInWindows(int [] num, int size) {
        ArrayList<Integer> result = new ArrayList<>();
        if (num == null || size <= 0 || size > num.length) {
            return result;
        }
        for (int i = 0; i <= num.length - size; i++) {
            result.add(maxNumOfInternal(num, i, i + size));
        }
        return result;
    }

    public int maxNumOfInternal(int[] num, int start, int end) {
        int result = num[start];
        for (int i = start + 1; i < end; i++) {
            result = Math.max(result, num[i]);
        }
        return result;
    }

思路二

经典的窗口求最值问题。思路为我们维护一个队头为当前窗口的最大值的双端队列。双端队列的两端均可增删节点。

首先是初始化窗口,因为初始化窗口的过程是扩展阶段,不需要弹出窗口左边的值。当队列为空时,直接进队列,此时队头就是最大值(就他自己)。接下来新加入的节点,若小于队头元素,说明它还是有可能成为窗口的最大值呢,前提是当队头的元素被剔除了,所以此时加入该节点。当新加入的节点,若大于当前队尾元素,说明该队尾永远都不可能成为窗口的最大值,所以剔除掉节点,继续比较新的队尾元素,直到队尾元素大于待加入节点。

当窗口初始化好之后,也就是窗口的右部分已扩展到指定位置。这时窗口就不要扩容了,只需不断向后滑动即可。滑动的过程当队列的大小等于窗口的大小要剔除头部的元素。加入新的节点策略依然要向初始化窗口阶段维护队头为最大值。该阶段记录窗口滑动过程中队头元素值即可,这就是题目要求的所有窗口的最大值集合。

这里有个问题,我们如何才能确保当前窗口确实要弹出头部节点呢?我们的策略可能会使窗口的大小不等于指定的size。这里解决的方案是队列存的不是值,而是索引,这样在加入节点,只需判断当前索引和队头最大值的索引是否等于size,若是则说明要弹出队头,否则按之前的策略判断加入节点即可。

/**
     * 维护一个队头为最大值的队列,单调递减队列
     * @param num
     * @param size
     * @return
     */
    public ArrayList<Integer> maxInWindows2(int [] num, int size) {
        ArrayList<Integer> result = new ArrayList<>();
        if (num == null || size <= 0 || size > num.length) {
            return result;
        }
        //初始化窗口
        LinkedList<Integer> windows = new LinkedList<>();
        for (int i = 0; i < size; i++) {
            while (windows.size() != 0 && num[windows.getLast()] <= num[i]) {
                windows.removeLast();
            }
            windows.addLast(i);
        }
        result.add(num[windows.getFirst()]);

        for (int i = size; i < num.length; i++) {
            if (i - windows.getFirst() == size) {
                windows.removeFirst();
            }
            while(windows.size() != 0 && num[windows.getLast()] <= num[i]) {
                windows.removeLast();
            }
            windows.addLast(i);
            result.add(num[windows.getFirst()]);
        }
        return result;
    }

总结

第二种方法将最大值维持在指定位置的思路值得借鉴,同样的还有单调栈问题。

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/84075370