题目信息
LeetoCode地址: . - 力扣(LeetCode)
题目理解
这是一道非常好的题目,同时考察了TreeMap, 单调队列,滑动窗口。
题意就是求连续子数组中最大最小元素差值绝对值不大于k的最长长度。
滑动窗口TreeMap写法
最直观的写法就是维护一个滑动窗口,并对窗口内的元素进行排序,这样就可以随时知道窗口内的最大绝对差。
给定长度为n的nums 数组,对于任意的左右下标l 和r, 0<=l<r<=n-1
当最大绝对差大于k时,我们便将窗口右移,并更新窗口内的元素。我们记录滑动过程中的最大长度,直到滑倒最右端。
以下面数组为例
nums = [10,1,2,4,7,2], limit = 5
滑动窗口初始左右下标l=0, r=0.
第一步,将r=0下标元素存入有序map= {10}, 因为只有一元素,最大绝对值为0. 滑块开始右移,r=r+1
第二步,将r=1下标元素存入有序map={1,10},此时最大绝对值为10-1=9,大于limit=5, 滑块左下标右移,l=1, 并将第一个元素10从有序map中移除,map={1}, 再次满足最大绝对值要求,
第三步,滑块继续右移,r=2, 存入map={1,2}. 最大长度为2.
第四步,滑块继续右移,r=3, 存入map={1,2,4}. 最大长度为3
第五步,滑块继续右移,r=4, 存入map={1,2,4,7}.此时最大绝对值为7-1=6,大于limit=5, 滑块左下标右移,l=2, 并将第二个元素1从有序map中移除,map={2,4,7}, 满足最大绝对值要求,最大长度为3.
第六步,滑块继续右移,r=5, 存入map={2,4,7,2}. 最大长度为4.
结束。
时间复杂度:O(nlogn),元素存入和移除有序Map的时间复杂度为O(logn),每个元素至多操作1次,共n次操作。
额外空间复杂度:O(n), 最眉笔的情况下,我们需要存入整个nums数组到有序Map。
class Solution {
public int longestSubarray(int[] nums, int limit) {
TreeMap<Integer, Integer> map = new TreeMap<>();
int length = nums.length, l = 0, r = 0, max = 0;
while (r < length) {
map.put(nums[r], map.getOrDefault(nums[r], 0)+1);
while (map.lastKey() - map.firstKey() > limit) {
map.put(nums[l], map.getOrDefault(nums[l], 0)-1);
if (map.get(nums[l]) == 0) {
map.remove(nums[l]);
}
l++;
}
max = Math.max(max, r-l+1);
r++;
}
return max;
}
}
滑动窗口+单调队列写法
上面写法的缺陷在于,存入和取出Map的时间复杂度有些高,可以将其优化为线性复杂度。
由于我们只关注滑动窗口最大值和最小值的变化,可以使用两个单调队列来保存这些信息,当滑动窗口左下标右移时,我们及时更新两个单调队列里的值便可。
类似的,我们以下面数组为例演示
nums = [10,1,2,4,7,2], limit = 5
滑动窗口初始左右下标l=0, r=0.单调递增队列minQue=[], 单调递减队列maxQue=[].
- l=0, r =0, 移除minQue队尾所有大于nums[r]=10的元素,并入队nums[r], minQue={10}; 类似的, 移除maxQue队尾所有小于nums[r]的元素并入队nums[r], maxQue={10}. 此时滑动窗口内的最大值是maxQue的队首10, 最小值是minQue的队尾10,最大差是0,我们无需调整滑动窗口的长度,因此最大长度为1.
- 滑块右扩,l=0,r=1;移除minQue队尾所有大于nums[r]=1的元素,并入队nums[r], minQue={1}; 类似的, 移除maxQue队尾所有小于nums[r]=1的元素并入队nums[r], maxQue={10,1}. 此时滑动窗口内的最大值是maxQue的队首10, 最小值是minQue的队尾1,最大差是9,我们调整滑动窗口的长度,右移l=1, 移除maxQue中的队首元素10,maxQue={1}, 此时最大差等于0, 因此最大长度保持1不变.
- 滑块继续右扩, l=1,r=2;更新minQue={1,2}, maxQue={2},最大差等于1,更新最大长度等于2.
- 滑块继续右扩,l=1,r=3; 更新minQue={1,2,4}, maxQue={4},最大差=3,最大长度=3.
- 滑块继续右扩,l=1,r=4; 更新minQue={1,2,4,7}, maxQue={7},最大差=6,我们开始右移l调整。右移l=2, 移除minQue队首元素1,minQue={2,4,7}, 最大差=5, 最大长度=4.
- 滑块继续右移,l=1,r=5, 更新minQue={2,2}, maxQue={7,2}, 最大差=5,最大长度=5
- 结束。
时间复杂度: O(n),没有元素重复出入队列,都是线性的
额外空间复杂度:O(n),我们维护了两个单调队列。
class Solution {
public int longestSubarray(int[] nums, int limit) {
LinkedList<Integer> maxQue = new LinkedList<>();
LinkedList<Integer> minQue = new LinkedList<>();
int length = nums.length, l = 0, r = 0, max = 0;
while (r < length) {
while (!maxQue.isEmpty() && maxQue.peekLast() < nums[r]) {
maxQue.pollLast();
}
while (!minQue.isEmpty() && minQue.peekLast() > nums[r]) {
minQue.pollLast();
}
maxQue.offer(nums[r]);
minQue.offer(nums[r]);
while (!maxQue.isEmpty() && !minQue.isEmpty() && maxQue.peekFirst() - minQue.peekFirst() > limit) {
if (minQue.peekFirst() == nums[l]) {
minQue.pollFirst();
}
if (maxQue.peekFirst() == nums[l]) {
maxQue.pollFirst();
}
l++;
}
max = Math.max(max, r-l+1);
r++;
}
return max;
}
}