ADT:单调队列(Leetcode1438)
题目描述
给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。
如果不存在满足条件的子数组,则返回 0 。
示例 1:
输入:nums = [8,2,4,7], limit = 4
输出:2
解释:所有子数组如下:
[8] 最大绝对差 |8-8| = 0 <= 4.
[8,2] 最大绝对差 |8-2| = 6 > 4.
[8,2,4] 最大绝对差 |8-2| = 6 > 4.
[8,2,4,7] 最大绝对差 |8-2| = 6 > 4.
[2] 最大绝对差 |2-2| = 0 <= 4.
[2,4] 最大绝对差 |2-4| = 2 <= 4.
[2,4,7] 最大绝对差 |2-7| = 5 > 4.
[4] 最大绝对差 |4-4| = 0 <= 4.
[4,7] 最大绝对差 |4-7| = 3 <= 4.
[7] 最大绝对差 |7-7| = 0 <= 4.
因此,满足题意的最长子数组的长度为 2 。
示例 2:
输入:nums = [10,1,2,4,7,2], limit = 5
输出:4
解释:满足题意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5 <= 5 。
示例 3:
输入:nums = [4,2,2,2,4,4,2,2], limit = 0
输出:3
解题
滑动窗口
很直观的就是使用双指针的滑动窗口实现,主要是需要维护一个数据结构来记录limit
先把抽象出ADT的滑动窗口实现写出来
class Solution {
public:
int longestSubarray(vector<int> &nums, int limit) {
MQ mq;
int maxLen = 0;
int left=0,right=0;
while (right<nums.size()){
mq.add(nums[right++]);
while (mq.limit()>limit){
mq.remove(nums[left++]);
}
maxLen = max(maxLen,right-left);
}
return maxLen;
}
};
重点在于实现一个数据结构,又add(int n)
、remove(int n)
、limit()
方法
使用堆实现
参考ADT:双优先队列,解决中位数问题#,使用堆+延迟删除实现
class Heap{
// 大顶堆
priority_queue<int, vector<int>, less<int>> big_heap;
// 小顶堆
priority_queue<int, vector<int>, greater<int>> small_heap;
unordered_map<int, int> big_delay_delete;
unordered_map<int, int> small_delay_delete;
public:
void add(int n){
big_heap.push(n);
small_heap.push(n);
}
int limit(){
return big_heap.top()-small_heap.top();
}
void remove(int n){
if(big_heap.top()==n){
big_heap.pop();
}else{
big_delay_delete[n]++;
}
while (big_delay_delete[big_heap.top()]>0){
big_delay_delete[big_heap.top()]--;
big_heap.pop();
}
if(small_heap.top()==n){
small_heap.pop();
}else{
small_delay_delete[n]++;
}
while (small_delay_delete[small_heap.top()]>0){
small_delay_delete[small_heap.top()]--;
small_heap.pop();
}
}
};
class Solution {
public:
int longestSubarray(vector<int> &nums, int limit) {
Heap heap;
int maxLen = 0;
int left=0,right=0;
while (right<nums.size()){
heap.add(nums[right++]);
while (heap.limit()>limit){
heap.remove(nums[left++]);
}
maxLen = max(maxLen,right-left);
}
return maxLen;
}
};
ac,但是效率极差,考虑到每次加入都需要logn的时间,思考能否用队列来实现
使用单调队列
class MQ{
// 大队列
deque<int> big_queue;
// 小队列
deque<int> small_queue;
public:
void add(int n){
while ((!big_queue.empty())&&big_queue.back()<n){
big_queue.pop_back();
}
big_queue.push_back(n);
while ((!small_queue.empty())&&small_queue.back()>n){
small_queue.pop_back();
}
small_queue.push_back(n);
}
int limit(){
return big_queue.front()-small_queue.front();
}
void remove(int n){
if(big_queue.front()==n){
big_queue.pop_front();
}
if(small_queue.front()==n){
small_queue.pop_front();
}
}
};
class Solution {
public:
int longestSubarray(vector<int> &nums, int limit) {
MQ mq;
int maxLen = 0;
int left=0,right=0;
while (right<nums.size()){
mq.add(nums[right++]);
while (mq.limit()>limit){
mq.remove(nums[left++]);
}
maxLen = max(maxLen,right-left);
}
return maxLen;
}
};
值得注意的是,这种单调队列ADT用的是两个双向队列deque,分别维护窗口内包含right指向数据的一个单调递减、单调递增队列
因为这个ADT的目的只是为了获取limit,所以无需记录每个数
效率明显变高了