滑动窗口9:239. 滑动窗口最大值和480滑动窗口中位数
Posted 纵横千里,捭阖四方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了滑动窗口9:239. 滑动窗口最大值和480滑动窗口中位数相关的知识,希望对你有一定的参考价值。
这两个题虽然是滑动窗口的 问题,但是解决思路更多需要堆等结构,不一定是双指针。
题目要求:
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
示例1:
输入:nums = [1,3,-1,-3,5,3,6,7], 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。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] pair1, int[] pair2) {
return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
}
});
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;
}
}
除此之外,还可以使用双向队列、单调队列来解决,该方法,我们到《单调栈和单调队列》一章再看。
480. 滑动窗口中位数
中位数是有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。
给你一个数组 nums,有一个长度为 k 的窗口从最左端滑动到最右端。窗口中有 k 个数,每次窗口向右移动 1 位。你的任务是找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。
示例:
给出 nums = [1,3,-1,-3,5,3,6,7],以及 k = 3。
窗口位置 中位数
--------------- -----
[1 3 -1] -3 5 3 6 7 1
1 [3 -1 -3] 5 3 6 7 -1
1 3 [-1 -3 5] 3 6 7 -1
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] 6
因此,返回该滑动窗口的中位数数组 [1,-1,-1,3,5,6]。
这道题官方的解释真是又臭又长,我们可以寻找一些更简洁易懂的思路来解决问题。
1.二分查找+冒泡排序
不要把问题想得太复杂,二分查找+冒泡排序完全可以解决
- 维护一个排过序的滑动窗口数组
- 使用二分查找检索删除的索引
- 将需要删除的值替换为需要插入的值
- 使用局部冒泡排序保证数组顺序
这个方法思想直接易懂,但是写起来有点长,而且要涉及多种算法,编写难度不低。
class Solution {
public double[] medianSlidingWindow(int[] nums, int k) {
double[] res = new double[nums.length - k + 1];
int[] window = new int[k];
//添加初始值
for (int i = 0; i < k; i++) {
window[i] = nums[i];
}
//初始的快排,懒得写直接调用
Arrays.sort(window);
res[0] = getMid(window);
//窗口滑动
for (int i = 0; i < nums.length - k; i++) {
//需要删除的数
int index = search(window, nums[i]);
//替换为需要插入的数
window[index] = nums[i + k];
//向后冒泡
while (index < window.length - 1 && window[index] > window[index + 1]) {
swap(window, index, index + 1);
index++;
}
//向前冒泡
while (index > 0 && window[index] < window[index - 1]) {
swap(window, index, index - 1);
index--;
}
res[i + 1] = getMid(window);
}
return res;
}
//交换
private void swap(int[] window, int i, int j) {
int temp = window[i];
window[i] = window[j];
window[j] = temp;
}
//求数组的中位数
private double getMid(int[] window) {
int len = window.length;
if (window.length % 2 == 0) {
//避免溢出
return window[len / 2] / 2.0 + window[len / 2 - 1] / 2.0;
} else {
return window[len / 2];
}
}
//最简单的二分查找
private int search(int[] window, int target) {
int start = 0;
int end = window.length - 1;
while (start <= end) {
int mid = start + (end - start) / 2;
if (window[mid] > target) {
end = mid - 1;
} else if (window[mid] < target) {
start = mid + 1;
} else {
return mid;
}
}
return -1;
}
}
在这个题里,我们就可以使用jdk的库函数来简化工作了,二分查找和排序都可以直接用了。代码如下:
class Solution {
public double[] medianSlidingWindow(int[] nums, int k) {
int len = nums.length - k + 1;
double[] res = new double[len];
int[] arr = Arrays.copyOfRange(nums, 0, k);
Arrays.sort(arr);
for (int i = 0; i < len; i++) {
if (k % 2 == 0) {
res[i] = (0.00 + arr[k/2] + arr[k/2-1]) / 2;
} else {
res[i] = arr[k/2];
}
if (i == len - 1) break;
update(arr, nums[i], nums[i + k]);
}
return res;
}
void update(int[] arr, int rm, int add) {
if (rm == add) return;
int idx = Arrays.binarySearch(arr, rm);
int i = 0;
if (add > rm) {
for (i = idx; i < arr.length - 1; i++) {
if (arr[i+1] >= add) break;
arr[i] = arr[i+1];
}
} else {
for (i = idx; i > 0; i--) {
if (arr[i - 1] <= add) break;
arr[i] = arr[i-1];
}
}
arr[i] = add;
}
}
以上是关于滑动窗口9:239. 滑动窗口最大值和480滑动窗口中位数的主要内容,如果未能解决你的问题,请参考以下文章