题目一看就是采用滑动窗口来解决,之所以记录一下是想复习一下单调队列的语法逻辑。
题干如下:
我们可以枚举每一个位置作为右端点,找到其对应的最靠左的左端点,满足区间中最大值与最小值的差不超过 limit。
注意到随着右端点向右移动,左端点也将向右移动,于是我们可以使用滑动窗口解决本题。
这里我们需要一个数据结构来存储滑动窗口中的元素,并且需要对滑动窗口的元素进行排序,在C++中,map和set就有这样的功能,考虑到滑动窗口中的元素有可能重复,所以这里采用C++ STL中的multiset数据结构。multiset允许出现重复元素,并且具有排序功能,通过multiset.begin()和multiset.end()-1就可以访问到最大最小元素了。
方法一:滑动窗口+有序集合
代码如下:
class Solution {
public: //参考官方解答,滑动窗口
int longestSubarray(vector<int>& nums, int limit) {
multiset<int> st; //有序集合
int n = nums.size();
int left = 0, right = 0; //滑动窗口
int res = 0;
while(right<n){
st.insert(nums[right]);
while(*(--st.end()) - *st.begin() > limit){ //条件判断
st.erase(st.find(nums[left]));
left++;
}
res = max(res,right - left + 1); //实时更新
right++;
}
return res;
}
};
方法二:滑动窗口+单调队列
在方法一中,我们仅需要统计当前窗口内的最大值与最小值,因此我们也可以分别使用两个单调队列解决本题。
在实际代码中,我们使用一个单调递增的队列 queMin 维护最小值,一个单调递减的队列queMax 维护最大值。这样我们只需要计算两个队列的队首的差值,即可知道当前窗口是否满足条件。
代码如下:
class Solution {
public: //力扣官方,滑动窗口+单调队列
int longestSubarray(vector<int>& nums, int limit) {
int n = nums.size();
deque<int> queMin, queMax; //单调队列
int left = 0, right = 0; //滑动窗口
int res = 0;
while(right < n){
while(!queMax.empty()&&queMax.back() < nums[right]) //单调递减队列
queMax.pop_back();
while(!queMin.empty()&&queMin.back() > nums[right]) //单调递增队列
queMin.pop_back();
queMin.push_back(nums[right]);
queMax.push_back(nums[right]);
while(!queMax.empty()&&!queMin.empty()&&queMax.front() - queMin.front() > limit){ //条件判断
if(queMin.front() == nums[left])
queMin.pop_front();
if(queMax.front() == nums[left])
queMax.pop_front();
left++;
}
res = max(res,right - left + 1);
right++;
}
return res;
}
};