给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/sliding-window-maximum
示例 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
对于每个滑动窗口,我们可以使用 O(k) 的时间遍历其中的每一个元素,找出其中的最大值。对于长度为 n 的数组 nums 而言,窗口的数量为 n-k+1,因此该算法的时间复杂度为 O((n-k+1)k)=O(nk),会超出时间限制
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//定义结果集
List<Integer> list = new ArrayList();
//定义窗口左右边界的指针
int left = 0,right = k-1;
while(right < nums.length){
//将窗口最大值添加到结果集
list.add(getMax(nums,left,right));
//通过指针移动窗口
right++;
left++;
}
//list转数组
return list.stream().mapToInt(Integer::intValue).toArray();
}
/**
* 获取数组指定区间的最大值
*/
public int getMax(int[] nums,int start,int end){
int max = nums[start];
for(int i = start+1;i<=end;i++){
max = Math.max(max,nums[i]);
}
return max;
}
}
对于「最大值」,我们可以想到一种非常合适的数据结构,那就是优先队列(堆),其中的大根堆可以帮助我们实时维护一系列元素中的最大值。
对于本题而言,初始时,我们将数组 nums 的前 k -1个元素放入优先队列中。每当我们向右移动窗口时,我们就可以把一个新的元素放入优先队列中,此时堆顶的元素就是堆中所有元素的最大值。然而这个最大值可能并不在滑动窗口中,在这种情况下,这个值在数组 nums 中的位置出现在滑动窗口左边界的左侧。因此,当我们后续继续向右移动窗口时,这个值就永远不可能出现在滑动窗口中了,我们可以将其永久地从优先队列中移除。
我们不断地移除堆顶的元素,直到其确实出现在滑动窗口中。此时,堆顶元素就是滑动窗口中的最大值。为了方便判断堆顶元素与滑动窗口的位置关系,我们可以在优先队列中存储二元组 (num,index),表示元素 num 在数组中的下标为 index。
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//定义大顶堆,堆内元素存储数组,数组第一位存储nums的值,第二位存储nums的下标
PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>(){
public int compare(int[] o1,int[] o2){
//根据nums值排序
return o2[0] - o1[0];
}
});
//初始化,先将前k-1个元素放到大顶堆中
for(int i=0;i<k-1;i++){
queue.offer(new int[]{nums[i],i});
}
//定义结果集
int[] ans = new int[nums.length - k + 1];
for(int i = k - 1;i<nums.length;i++){
queue.offer(new int[]{nums[i],i});
while(queue.peek()[1] < i - k + 1){
queue.poll();
}
ans[i-k +1] = queue.peek()[0];
}
return ans;
}
}
复杂度分析
时间复杂度:O(nlogn),其中 n 是数组 nums 的长度。在最坏情况下,数组 nums 中的元素单调递增,那么最终优先队列中包含了所有元素,没有元素被移除。由于将一个元素放入优先队列的时间复杂度为 O(logn),因此总时间复杂度为 O(nlogn)。
空间复杂度:O(n),即为优先队列需要使用的空间。这里所有的空间复杂度分析都不考虑返回的答案需要的 O(n) 空间,只计算额外的空间使用。
当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。为了保持队列的性质,我们会不断地将新的元素与队尾的元素相比较,如果前者大于等于后者,那么队尾的元素就可以被永久地移除,我们将其弹出队列。我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素。
由于队列中下标对应的元素是严格单调递减的,因此此时队首下标对应的元素就是滑动窗口中的最大值。但与方法一中相同的是,此时的最大值可能在滑动窗口左边界的左侧,并且随着窗口向右移动,它永远不可能出现在滑动窗口中了。因此我们还需要不断从队首弹出元素,直到队首元素在窗口中为止。
为了可以同时弹出队首和队尾的元素,我们需要使用双端队列。满足这种单调性的双端队列一般称作「单调队列」
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//定义单调队列
MonotoneQueue window = new MonotoneQueue();
//定义结果集
int[] ans = new int[nums.length - k + 1];
for(int i = 0;i<nums.length;i++){
if(i<k-1){
//初始化,将前k-1个元素放到单调队列中
window.push(nums[i]);
}else{
window.push(nums[i]);
//存储结果集
ans[i - k + 1] = window.max();
//保证队列少于k个
window.pop(nums[i-k+1]);
}
}
return ans;
}
public class MonotoneQueue{
Deque<Integer> deque = new LinkedList<>();
//尾部添加元素
public void push(int n){
while (!deque.isEmpty() && deque.peekLast() < n){
deque.pollLast();
}
deque.addLast(n);
}
//弹出队列顶部元素
public void pop(int n){
if(deque.peekFirst() == n){
deque.pollFirst();
}
}
//获取队列最大值
public int max(){
return deque.peekFirst();
}
}
}
复杂度分析
**时间复杂度:**O(n),其中 n 是数组nums 的长度。每一个下标恰好被放入队列一次,并且最多被弹出队列一次,因此时间复杂度为 O(n)。
**空间复杂度:**O(k)。与方法二不同的是,在方法三中我们使用的数据结构是双向的,因此「不断从队首弹出元素」保证了队列中最多不会有超过 k 个元素,因此队列使用的空间为 O(k)。
扩展:ArrayList和LinkedList的区别
1. ArrayList是实现了基于动态数组的数据结构,而LinkedList是基于双向链表的数据结构;
2. 对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针;
3. 对于添加和删除操作add和remove,一般大家都会说LinkedList要比ArrayList快,因为ArrayList要移动数据
一般首选用ArrayList,由于LinkedList可以实现栈、队列以及双端队列等数据结构,所以当特定需要时候,使用LinkedList
给定一个字符串 s
,请你找出其中不含有重复字符的 **最长子串 **的长度。
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/
提示:双指针
1638.绝对差不超过限制的最长连续子数组
给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。
如果不存在满足条件的子数组,则返回 0 。
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit 提示:使用两个单调队列
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。