算法通关村 —— 滑动窗口与堆结合

堆与滑动窗口问题的结合

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位,返回滑动窗口中的最大值

输入: nums = [1,3,-1,-3,5,3,6,71,k = 3
输出: [3,3,5,5,6,7] 
解释: 滑动窗口的位置           最大值
[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

这种方法在基础算法的堆部分出现过。对于最大值、K个最大这种场景优先队列 (堆)是首先应该考虑的思路。大根堆可以帮助我们实时维护一系列元素中的最大值。

本题初始时,我们将数组 nums 的前 k个元素放入优先队列中。每当我们向右移动窗口时,我们就可以把一个新的元素放入优先队列中,此时堆顶的元素就是堆中所有元素的最大值。然而这个最大值可能并不在滑动窗口中,在这种情况下,这个值在数组 nums 中的位置出现在滑动窗口左边界的左侧。因此,当我们后续继续向右移动窗口时,这个值就永远不可能出现在滑动窗口中了,我们可以将其永久地从优先队列中移除

我们不断地移除堆顶的元素直到其确实在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。

为了方便判断堆顶元素与滑动窗口的位置关系,我们可以在优先队列中存储二元组(num,index),表示元素 num 在数组中的下标为 index。

public class MaxSlidingWindow {
    public static int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        // 定义最大堆(优先队列),比较的是num值,num值相等再比较索引
        PriorityQueue pq = new PriorityQueue(new Comparator() {
            public int compare(int[] pair1, int[] pair2) {
                return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
            }
        });
        // 先入堆k个
        for (int i = 0; i < k; ++i) {
            pq.offer(new int[]{nums[i], i});
        }
        int[] ans = new int[n - k + 1]; // 存储滑动窗口中的最大值
        ans[0] = pq.peek()[0];
        for (int i = k; i < n; ++i) {
            // 继续入堆
            pq.offer(new int[]{nums[i], i});
            // 如果最大值的索引不在滑动窗口内,则将其出堆,直到其在滑动窗口内
            while (pq.peek()[1] <= i - k) {
                pq.poll();
            }
            // 存储当前滑动窗口最大值
            ans[i - k + 1] = pq.peek()[0];
        }
        return ans;
    }
    // 测试
    public static void main(String[] args) {
        int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
        int k = 3;
        int[] result = maxSlidingWindow(nums, k);
        System.out.println(Arrays.toString(result));
    }

}

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