ADT:单调队列(Leetcode1438)

ADT:单调队列(Leetcode1438)

题目描述

1438. 绝对差不超过限制的最长连续子数组

给你一个整数数组 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;
    }
};

image-20210221113142497

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,所以无需记录每个数

adt_1438

image-20210221113232103

效率明显变高了

原文地址:https://www.cnblogs.com/cpaulyz/p/14425314.html