剑指offer59 -I. 滑动窗口的最大值
一.题目
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
示例:
输入: 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 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
二.解题思路
2.1 暴力法
设窗口区间为[i,j],最大值为xj,当窗口向前移动一格,则区间变为[i+1,j+1],即添加了nums[j+1],删除了nums[i]
因为删除的nums[i]可能恰好是窗口内唯一的最大值xj,因此不能通过以上方法计算xj+1,必须使用o(j-i)时间,遍历整个窗口区间获取最大值,即:
xj+1 = max(nums(i+1),…num(j+1))
此暴力法 的时间复杂度为O((n-k+1)k)=O(nk)
设数组 numsnums 的长度为 nn ,则共有 (n−k+1) 个窗口;
获取每个窗口最大值需线性遍历,时间复杂度为 O(k) 。
class Solution{
public int[] maxIndex(int [] nums,int k){
if (nums.length ==0) return new int[0];
int [] res= new int[nums.length-k+1];
for (int i = 0;i<nums.length-k+1;i++){
int Max = Integer.MIN_VALUE;
for (int j=i;j<i+k;j++){
Max = Math.max(Max,nums[j]);
}
res[i] = Max;
}
return res;
}
2.2 最佳解法
本题难点:如何在每次窗口滑动后,将“获取窗口内最大值”的时间复杂度从O(k)降低至O(1)。
窗口对应的数据结构为双端队列,本题使用单调队列即可解决以上问题。遍历数组时,每轮保证单调队列deque:
1.deque内仅包含窗口内的元素
每轮窗口滑动移除了元素nums[i-1],需将deque内的对应元素一起删除。
2.deque内的元素非严格递减
每轮窗口滑动添加了元素nums[j+1],需将deque内所有<nums[j+1]的元素删除。
class Solution{
public int[] maxIndex(int []nums,int k){
int res = new int[nums.length-k+1];
Deque<Integer> deque = new LinkedList<>();
int index = 0;
for (int i =0;i<k;i++){
while(!deque.isEmpty()&&nums[i]>deque.peekLast())
deque.removeLast();
deque.addLast(nums[i]);
}
res[index++] = deque.peekFirst();
for (int i =k;i<nums.length;i++){
if (deque.peekFirst == nums[i-k]) deque.removeFirst();
while(!deque.isEmpty() && nums[i]>deque.peekLast()) deque.removeLast();
deque.addLast(nums[i]);
res[index++] =deque.peekFirst();
}
return res;
}
}
三.总结
碰到滑动窗口问题,考虑使用双向队列解题,提高时间复杂度的关键在于如何“获取窗口内最大值”。