【LeetCode-1438】绝对差不超过限制的最长连续子数组
问题
给你一个整数数组 nums,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于limit 。
如果不存在满足条件的子数组,则返回0。
示例
输入: nums = [10,1,2,4,7,2], limit = 5
输出: 4
解释: 满足题意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5 <= 5。
解答1:使用multiset
class Solution {
public:
int longestSubarray(vector<int>& nums, int limit) {
multiset<int> s;
int left = 0, right = 0, res = 0;
while (right < nums.size()) {
s.insert(nums[right++]);
while (*s.rbegin() - *s.begin() > limit) // 即最大值-最小值>limit
s.erase(s.find(nums[left++])); // 注意erase有两个原型,这里只删一个,传入迭代器
res = max(res, right - left);
}
return res;
}
};
重点思路
C++中,set/multiset/map内部元素是有序的,它们都基于红黑树实现。其中set会对元素去重,而multiset可以有重复元素,map是key有序的哈希表。本题使用multiset存储数据,其中rbegin为最右子节点,对应最大值;begin为最左子节点,对应最小值。
另外,在第8行的while语句中,不需要额外判断left < right
,因为当left = right
时,最大值减最小值必定等于0,而limit为大于等于0的数,所以可以省略这个判断条件。
最后需要注意的是,multiset
中的erase
方法可以传入值和迭代器位置。当传入为值时,会删除所有等于该值的节点;如果只想删一个,那么就使用find
函数找到一个迭代器指针,指针指向最早加入的一个。
解答2:使用单调队列
class Solution {
public:
int longestSubarray(vector<int>& nums, int limit) {
deque<int> max_d, min_d; // max_d单减,min_d单增
int left = 0, right = 0, res = 0;
while (right < nums.size()) {
while (!max_d.empty() && nums[right] > max_d.back())
max_d.pop_back();
while (!min_d.empty() && nums[right] < min_d.back())
min_d.pop_back();
max_d.push_back(nums[right]);
min_d.push_back(nums[right]);
right++;
while (max_d.front() - min_d.front() > limit) {
if (nums[left] == max_d.front()) max_d.pop_front();
if (nums[left] == min_d.front()) min_d.pop_front();
left++;
}
res = max(res, right - left);
}
return res;
}
};
重点思路
multiset插入和删除的时间复杂度为O(log(N)),而使用两个双端队列实现的单调递增递减队列插入和删除的时间复杂度为O(1),还能更省内存。max_d
为一个单调递减序列,队首为窗口中的最大值;min_d
为一个单调递增序列,队首为窗口中的最小值。每一轮刚开始时需要将窗口最右的值加入两个队列中,此时需要删除队尾不符合单调性的值。
可以从本题总结滑动窗口算法的基本框架,核心是两层while循环,第一层while保证右指针正确遍历,第二层while保证窗口中的序列满足题目规定的limit条件。