题目:滑动窗口最大值
答案:
参考链接:详细通俗的思路分析,多解法
1.优先队列
建立大顶堆,保证堆顶是最大值。每次移除元素时间复杂度O(k)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
if (nums == null || n == 0 || k <= 0 || n < k) {
return nums;
}
int result[] = new int[n - k + 1];
int index = 0;
PriorityQueue<Integer> q = new PriorityQueue(k, Comparator.reverseOrder());
for (int i = 0; i < n; i++) {
if (q.size() >= k) { //移除上一个窗口的第一个元素
q.remove(nums[i - k]);
}
q.add(nums[i]);
if (i >= k - 1) { //取最大值,更新result
result[index++] = q.peek();
}
}
return result;
}
}
注意:
PriorityQueue默认为升序(小顶堆)
升序:Comparator.naturalOrder()
降序:Comparator.reverseOrder()
2.双端队列
ArrayDeque继承自Deque接口,可以从队列首部插入/取出,也可以从队列尾部插入/取出。
保持队列单调递减(队首元素即为最大值):
如果当前元素比队列的最后一个元素大,那么就将最后一个元素出队,重复这步直到当前元素小于队列的最后一个元素或者队列为空。
如果当前元素小于等于队列的最后一个元素或者队列为空,那么就直接将当前元素入队。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> max = new ArrayDeque<>();
int n = nums.length;
if (n == 0) {
return nums;
}
int result[] = new int[n - k + 1];
int index = 0;
for (int i = 0; i < n; i++) {
if (i >= k) { //判断队首元素是否是上一滑动窗的第一个元素,是则删除
if (max.peekFirst() == nums[i - k]) {
max.removeFirst();
}
}
while (!max.isEmpty() && nums[i] > max.peekLast()) { //保持队列单调递减
max.removeLast();
}
max.addLast(nums[i]);
if (i >= k - 1) { //队首元素即为最大值,更新result
result[index++] = max.peekFirst();
}
}
return result;
}
}
3.动态规划
把数组 k 个一组就行分组。
nums = [1,3,-1,-3,5,3,6,7], and k = 3
| 1 3 -1 | -5 4 3 | 5 7 |
如果我们要求 result[1],也就是下边 i 到 j 范围内的数字的最大值
| 1 3 -1 | -5 4 3 | 5 7 |
^ ^
i j
i 到 j 范围内的数字被分割线分成了两部分
如果我们知道了左半部的最大值和右半部分的最大值,那么两个选最大的即可。
左半部分的最大值,也就是当前数到它右边界范围内的最大值。
用 rightMax[i] 保存 i 到它的右边界范围内的最大值,只需要从右到左遍历一遍就可以求出来了。
同理,右半部分的最大值,也就是当前数到它左边界范围内的最大值。
用 leftMax[i] 保存 i 到它的左边界范围内的最大值,只需要从左到右遍历一遍就可以求出来。
有了上边的两个数组,当前范围的两个边界 i 和 j,rightMax[i] 存储的就是左半部分(i 到右边界)的最大值,leftMax[j] 存储的就是右半部分(j 到左边界)的最大值。result[i] 的结果也就出来了。
result[i] = Math.max(rightMax[i], leftMax[j]);
具体举例:
nums = [1,3,-1,-3,5,3,6,7], and k = 3
| 1 3 -1 | -5 4 3 | 5 7 |
rightMax: 3 3 -1 4 4 3 7 7 (从右往左计算更新)
leftMax: 1 3 3 -5 4 4 5 7 (从左往右计算更新)
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
if (n == 0) {
return nums;
}
//当前数到自己的左边界的最大值
int leftMax[] = new int[n];
int max = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
if (i % k == 0) { //每到一个边界就重置最大值
max = Integer.MIN_VALUE;
}
if (max < nums[i]) {
max = nums[i];
}
leftMax[i] = Math.max(nums[i], max);
}
//当前数到自己的右边界的最大值
int rightMax[] = new int[n];
max = Integer.MIN_VALUE;
for (int i = n - 1; i >= 0; i--) {
if (i % k == 0) { //每到一个边界就重置最大值
max = Integer.MIN_VALUE;
}
if (max < nums[i]) {
max = nums[i];
}
rightMax[i] = Math.max(nums[i], max);
}
int result[] = new int[n - k + 1];
for (int i = 0; i < result.length; i++) {
int j = i + k - 1;
result[i] = Math.max(rightMax[i], leftMax[j]);
}
return result;
}
}