leetcode. [239]滑动窗口最大值(B-F(暴力实现),PQ(优先队列),Deque(双端队列),DP(动态规划))
分析:
我们将数组从0扫描到n-1,将“promising”元素保留在双端队列中。 每个元素放置一次并轮询一次时,该算法将摊销O(n)。
在每个i处,我们保留“promising”元素,它们可能是窗口[i-(k-1),i]或任何后续窗口中的最大数量。 这意味着
如果双端队列中的某个元素不在i-(k-1)范围内,我们将其丢弃。 我们只需要从头开始轮询,因为我们使用的是双端队列,并且元素按数组中的序列排序
现在,只有[i-(k-1),i]中的那些元素在双端队列中。 然后,我们从尾部丢弃小于a [i]的元素。 这是因为如果a [x] <a [i]和x <i,则a [x]没有机会成为[i-(k-1),i]或任何其他后续窗口中的“最大值” :a [i]将永远是一个更好的候选人。
结果,双端队列中的元素按数组中的顺序及其值进行排序。 在每一步,双端队列的头部都是[i-(k-1),i]中的max元素
代码:
其中:
public E peek():检索但不删除此列表的头部(第一个元素)。
public E peekFirst():检索但不删除此列表的第一个元素,如果此列表为空,则返回null。
public E peekLast():检索但不删除此列表的最后一个元素,如果此列表为空,则返回null。
方法一:
- 双端队列实现:
class Solution {
if (nums == null || nums.length < 2)
return nums;
//双向队列,保存当前窗口最大值的数组位置 保证队列中数组位置的数值按从大到小排序
LinkedList<Integer> queue = new LinkedList<>();
//结果数组
int[] result = new int[nums.length - k + 1];
//遍历nums数组
for (int i = 0; i < nums.length; i++) {
//保证从大到小 如果前面数小则需要依次弹出,直至满足要求
while (!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]){
queue.pollLast();
}
//添加当前值对应的数组下标
queue.addLast(i);
//判断当前队列中队首的值是否有效
if (queue.peek() <= i - k){
queue.poll();
}
//当窗口长度为k时,保存当前窗口中最大值
if (i+1 >= k){
result[i+1-k] = nums[queue.peek()];
}
}
return result;
}
}
//leetcode submit region end(Prohibit modification and deletion)
方法二:暴力
最简单直接的方法是遍历每个滑动窗口,找到每个窗口的最大值。一共有 N - k + 1 个滑动窗口,每个有 k 个元素,于是算法的时间复杂度为 {O}(N k),表现较差。
public int[] maxSlidingWindow(int[] nums, int k) {
//暴力实现 B-F
int n = nums.length;
if (n * k == 0)
return new int[0];
int[] output = new int[n - k + 1];
for (int i = 0; i < n - k + 1; i++) {
int max = Integer.MIN_VALUE;
for (int j = i; j < i + k; j++) {
max = Math.max(max , nums[j]);
}
output[i] = max;
}
return output;
方法三:动态规划
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
if (n * k == 0) return new int[0];
if (k == 1) return nums;
int [] left = new int[n];
left[0] = nums[0];
int [] right = new int[n];
right[n - 1] = nums[n - 1];
for (int i = 1; i < n; i++) {
// from left to right
if (i % k == 0) left[i] = nums[i]; // block_start
else left[i] = Math.max(left[i - 1], nums[i]);
// from right to left
int j = n - i - 1;
if ((j + 1) % k == 0) right[j] = nums[j]; // block_end
else right[j] = Math.max(right[j + 1], nums[j]);
}
int [] output = new int[n - k + 1];
for (int i = 0; i < n - k + 1; i++)
output[i] = Math.max(left[i + k - 1], right[i]);
return output;
}
}