LeetCode 1438
方法1:TreeMap
时间复杂度:O(nlogn) 想法:反正我当时做这道题一看就想到滑动窗口,因为是什么longest subarray with xxxxxx,长得就有点像。那么问题无非就是怎样在滑动窗口滑的时候动态求解整个窗口内部的最大值和最小值。比较容易想到的就是TreeMap,因为TreeMap的key是有序的,可以直接拿firstKey和lastKey。那么套上滑动窗口那个模板就完了,外面for i,里面while j。 代码:
class Solution {
public int longestSubarray(int[] nums, int limit) {
int i = 0, j = 0, n = nums.length;
TreeMap<Integer, Integer> map = new TreeMap<>();
int res = 0;
for (; i < n; i++) {
while (j < n && (map.isEmpty() || (!map.isEmpty() && Math.abs(nums[j] - map.firstKey()) <= limit && Math.abs(nums[j] - map.lastKey()) <= limit))) {
int tmp = map.getOrDefault(nums[j], 0) + 1;
map.put(nums[j], tmp);
j++;
}
res = Math.max(res, j - i);
int tmp = map.get(nums[i]) - 1;
if (tmp == 0) map.remove(nums[i]);
else {
map.put(nums[i], tmp);
}
}
return res;
}
}
方法2:单调队列
时间复杂度:O(n)
出处:学习自花花酱zxi.mytechroad.com/blog/queue/…
想法:这个方法才是这道题值得学习的地方。在求窗口的最大值和最小值的时候,我们使用单调队列。事实上在Java里面是用的双端队列Deque。比方说那个我们用来动态取最大值队列,我们叫它maxQ。那么maxQ,每次往里放元素的时候,是从last这一端放,我们要维护这个队列从first到last这样数过来是单调不增的(差分<=0),那如果是这样的话,每次从first这一端拿元素,拿到的元素就会是这里面的最大值。那么为什么可以使用单调不增,而不是单调递减呢?这里如果在滑动窗口往右滑的时候,如果采用单调不增的队列,即允许相邻两个元素相等,那么在删除左边元素时,我们可以直接写if (maxQ.getFirst() == nums[l]) maxQ.pollFirst();,因为如果有好几个跟最大值相等的值堆在队列的first这一端,比方说是k个这样的元素,那么我反正知道这个窗口里面总共有k个这样的值,把nums[l]删掉也不要紧,反正删掉之后从这个队列里拿出来的最大值还是这个值,因为窗口里面还有。这样写的话用单调不增会比较方便一点。
代码:
class Solution {
public int longestSubarray(int[] nums, int limit) {
int res = 0, l = 0;
Deque<Integer> maxQ = new ArrayDeque<>();
Deque<Integer> minQ = new ArrayDeque<>();
for (int r = 0; r < nums.length; r++) {
while (!maxQ.isEmpty() && maxQ.getLast() < nums[r]) {
maxQ.pollLast();
}
while (!minQ.isEmpty() && minQ.getLast() > nums[r]) {
minQ.pollLast();
}
minQ.addLast(nums[r]);
maxQ.addLast(nums[r]);
while (maxQ.getFirst() - minQ.getFirst() > limit) {
if (maxQ.getFirst() == nums[l]) maxQ.pollFirst();
if (minQ.getFirst() == nums[l]) minQ.pollFirst();
l++;
}
res = Math.max(res, r - l + 1);
}
return res;
}
}