滑动窗口
题目描述:
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
思路:
最开始的思路是使用动态规划,当新加入一个元素,新加入的元素如果大于原来窗口最大值,那就可以直接更新为当前窗口最大值,不用考虑左边元素出窗口的问题,否则需要考虑窗口最左边的元素,如果是这个元素等于上一个窗口的最大值,那就重新找当前窗口的最大值(利用冒泡),如果不等于,那说明元素的最大值仍旧在窗口内。
但是这样做为无法通过最后一个测试用例会超出时间限制,即数组中元素按照从大到小排序且多个重复元素,就会执行多次冒泡。
后面参考了题解的思路,
https://leetcode.cn/problems/sliding-window-maximum/solution/you-xian-dui-lie-zui-da-dui-dan-diao-dui-dbn9/
滑动窗口:
我们需要的数据结构:
尾部可插入与删除元素(排除普通队列),但是又需要能直接获取头部元素(排除栈),且需要删除头部元素
选用list来实现这种数据结构
每加入一个新元素时我们从队尾开始,若大于队尾元素则删除队尾元素,直至小于队尾元素才加入这个新元素(这些队尾元素值比当前元素小且在当前元素左边,不可能成为之后窗口的最大值,所以可以直接淘汰
那么list中元素就是按照从大到小排序,且右边的元素下标大于左边的元素下标。
当我们添加完新元素,需要找当前窗口最大值,就可以查看list中下标为0的元素,,若该元素下标小于当前窗口的左边界,可以删除该元素,知道下标为0的元素在窗口内,取该元素作为当前窗口最大值。(该元素之前的那些元素虽然值大,但是已经不在窗口内了,不可能再作为之后的窗口最大值,所以可以直接淘汰。)
代码:
if(k==1)return nums;
int[] res = new int[nums.length-k+1];
//存储元素下标,list中元素按照从大到小排列
List<Integer> list = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
if(list.isEmpty()){
//第一个元素直接添加
list.add(i);
continue;
}
if(nums[i]<nums[list.get(list.size()-1)]){
//小于队尾元素则直接添加
list.add(i);
}else{
//从队尾开始删除所有比当前元素小的元素然后再插入当前元素,
// 这样就能保证list一定是降序排列,且当前元素成功插入list
while((!list.isEmpty())&&nums[list.get(list.size()-1)]<=nums[i]){
list.remove(list.size()-1);
}
list.add(i);
}
if(i>=k-1){
//若i<k-1,说明窗口还没到k
//否则判断list头部元素是否在窗口内,
// 若在则直接作为当前窗口最大值,若不在则删除直至头部元素在窗口内
int cur = i - k + 1;
while(list.get(0)<cur){
list.remove(0);
}
res[i-k+1] = nums[list.get(0)];
}
}
return res;