[Leetcode] 239. Sliding Window Maximum

这题其实挺老的,我刚毕业那段就有。但还是不是很容易,如果你要的是O(n)的时间。
hard的题在我心里面分为两种,一种是属于烦人的,另一种是属于需要巧妙思维的。这个是属于后者的。
这个题目,O(kn)的时间是当然很容易。就是window每右移一个就扫这个window得到其中最大的数即可。
O(nlogk)也是很明显的,就是用TreeMap<Integer(num), Integer(count)>保存这个window就可以了,TreeMap的lastKey是可以返回最大的key的,所以window每次右移一格的时候,就去掉window移开的那个数,加进去右边移进去的数字,然后每次用lastKey得到最大值就可以了。
这两种做法都太简单了,所以我就不写了。

现在将要介绍的是伟大的O(n)的做法。

跟往常一样,让我用一个例子来做说明,譬如1, 3, -1, -3, 5, 3, 2, 1, 2我们把k设成4好了
a. [1, 3, -1, -3], 5, 3, 2, 1, 2  在这个第一个window里,你会发现我们需要的数据其实就只有后面三个,就是[3,-1,-3],因为当前window也好再下一个window也好,3都比1大,我们已经不希望再见到这个1了。但是-1, -3还是有需要了解的。因为你不知道后面进来的是不是更小的数字。 
b. 1, [3, -1, -3, 5], 3, 2, 1, 2 在这个window里面,你会发现,3, -1, -3我们都没必要再见到了,因为5比它们都大,而且还在它们后面。所以这个时候我们需要记录的就是一个5就好了。也就是除去被window移开的1,3, -1, -3这三个数字我们其实完全都不需要理会和记录了。这个时候我们可以有一个延伸的理念,mini max window,简称小window。在这个小window里面,如果后来者比前面的数字要大,那么前面的数字就不在里面。这个时候小window里面只有[5]。顺便说一下,上面一步里,我们的小window是[3, -1, -3]
c. 1, 3, [-1, -3, 5, 3], 2, 1, 2 在这个window里面,虽然新进来的3比5小,但是因为我们不知道当5移出去的时候它是不是最大的。所以这个时候的小window里面就是[5, 3]。
d. 1, 3, -1, [-3, 5, 3, 2], 1, 2 在这个window里面,跟上面一样,2虽然比前面都小,但你不知道后面的数字会怎样,所以小window里面是[5, 3, 2]
e. 1, 3, -1, -3, [5, 3, 2, 1], 2 同理,此时小window就是[5, 3, 2, 1]了。
f. 1, 3, -1, -3, 5, [3, 2, 1, 2]  在这个window里面,我们沉痛的发现最大的5离开了我们,所以小window里面不能再有它了。与此同时,2比它的前辈1要大,所以小window就成为[3, 2, 2]了。

此时,你会神奇的发现,这些小window的第一个元素的集合,就是我们要求的解集。
所以,整个解题的过程就变成如何维护这个小window
其实也不难,只需要一个双向队列(其实就是一个链表)作为mini window即可。
window每一次移动
1. 判断小window的第一个数是不是已经脱离了当前window的范围了,是,就把它从头移出去
2. 将小window里比当前window新进来的数字小的数字全部移出去。
3. 将当前window新进来的数字放进小window的尾部
4. 将小window的第一个数字放进结果数组。

因为要做到第一点,我们放进window里的最好是index,是原来的数字也可以,只是操作上更麻烦一点点而已。根据以上算法,给出代码如下:

    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums.length == 0 || k == 0 || nums.length < k) return new int[0];
        int[] result = new int[nums.length - k + 1];
        Deque<Integer> cache = new LinkedList<>();
        for (int i = 0; i < nums.length; i++) {
            while (!cache.isEmpty() && nums[cache.peekLast()] < nums[i]) {
                cache.pollLast();
            }
            
            if (!cache.isEmpty() && i - k >= cache.peekFirst()) cache.pollFirst();
            cache.add(i);
            if (i >= k - 1) {
                result[i - k + 1] = nums[cache.peekFirst()];
            }
        }
        
        return result;
    }

代码上还需要注意的一点就是if (i >= k - 1)这一步。只有当这个条件符合了,我们才正式建立好第一个window,for循环前面的k个操作都还是在建立第一个window(不是小window,是大window),建立好了才需要放数字进结果里。

猜你喜欢

转载自blog.csdn.net/chaochen1407/article/details/81161983