代码手记笔录——优先队列与单调队列

优先队列

参考:https://zhuanlan.zhihu.com/p/478887055

优先队列采用堆结构存储数据,高优先级大顶堆。 可以重写比较器 cmp 设定优先级别。对于基本数据类型,默认为数值大的优先级高:

// 二者等价,数值大的优先级高
priority_queue<int>  prq;   // (1)
priority_queue<int, vector<int>, less<int>>  prq;   // (2)

// 二者等价,设置数值小的优先级高
priority_queue<int, vector<int>, less<int>>  prq;   // (1)
priority_queue<int, vector<int>, cmp>  prq;   // (2)
bool cmp(int &a, int &b) {
  return a > b;
}

对于类对象类型的 cmp 重写方式如下:

struct cmp {  // 必须是 struct 类型
  bool operator()(fruit& a, fruit &b) {   // 必须重写 () 运算符
      return fruit.price > fruit.price;
  }
}

优先队列适合求局部最值问题。

经典例题

239 滑动窗口最大值


(1)将窗口内元素放入优先队列,并每次取队头元素即可得到最大值;
(2)删除不在窗口的队头元素,即可保证每次队头元素为局部最大值。

  • 时间复杂度分析:每个元素只入队、出队一次,故为 O(n)。
  • 空间复杂度分析: O(n)
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        priority_queue<pair<int, int>> prq;
        vector<int> vec;
        int n = nums.size(), topN, topIdx;
        for (int i=0; i<k; ++i)
            prq.push(make_pair(nums[i], i));
        vec.push_back(prq.top().first);  // 滑动窗口移动启动时的最大值
        for (int i=k; i<n; ++i) {
            prq.push(make_pair(nums[i], i));
            // 若当前优先队列的最大值不在窗口内,就让其 pop 掉
            while (prq.top().second <= i-k)
                prq.pop();
            vec.push_back(prq.top().first);
        }
        return vec;
    }
};

单调队列

参照:https://blog.51cto.com/u_13281972/2997983
一个序列只有头尾的数据有变动,需要求该序列的最大值或最小值,可以尝试使用单调队列。

经典例题

239 滑动窗口最大值


算法思想:单调队列里存放数组下标,且保证下标从小到大排序,同时保证下标对应的元素也从小到大排序。队列里值大者放前,每指向一元素,就从队尾入手将队列里小于该元素值的数据弹出【队尾弹出机制保证了下标对应的元素也从小到大排序】。滑动窗口从左向右移动【移动方向保证了下标从小到大排序】。选取最大值时需弹出队头以确保落在滑动窗口内【队头弹出机制保证了所求最大值落在滑动窗口内】。

  • 时间复杂度分析:每个元素只入队、出队一次,故为 O(n)。
  • 空间复杂度分析:单调队列元素最多不超过滑动窗口个数,为 O(k)
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> dq;
        vector<int> vec;
        for (int i=0; i<k; ++i) {
            while (!dq.empty() && nums[dq.back()] <= nums[i])
                dq.pop_back();
            dq.push_back(i);
        }
        vec.push_back(nums[dq.front()]);
        for (int i=k; i<nums.size(); ++i) {
            // 单调队列每插入一个元素,都将其前面小于它的元素弹出
            while (!dq.empty() && nums[dq.back()] <= nums[i])
                dq.pop_back();
            dq.push_back(i);
            // 由于窗口在移动,因此每移动一次,就将不在窗口内的元素弹出
            while (dq.front() <= i-k)
                dq.pop_front();
            vec.push_back(nums[dq.front()]);
        } 
        return vec;
    }
};

501 二叉搜索树中的众数

单调队列的思想也适合解决这道题。由于不能使用额外的空间,我们将 vector 的功能也按照队列的方式实现,就不需要额外增添 deque 的空间。
算法思想:用pair<preVal, preFreq>记录上一个可能联系序列的 value 及frequency,用传参的方式维护 maxFreq。若当前元素与preVal相同,就让preFreq加 1;否则令preVal=curRoot->val,preFreq=1。若 preFreq 超过了maxFreq,则放入答案数组,并更新 maxFreq。这里要注意的是,由于有多个可能的众数,所以若频率等于 maxFreq,则直接放入答案数组;若超过maxFreq,则需要令答案数组置空,再放入答案数组。

  • 进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)
class Solution {
public:
    vector<int> findMode(TreeNode* root) {
        vector<int> ans;
        int maxFreq = 0;
        pair<int, int> preRecord = make_pair(INT_MIN, 0);
        findMode(ans, root, preRecord, maxFreq);      
        return ans;  
    }
private:
    void findMode(vector<int> &ans, TreeNode* root, pair<int, int> &preRecord , int &maxFreq) {
        if (root->left)
            findMode(ans, root->left, preRecord, maxFreq);
        if (root->val == preRecord.first) 
             preRecord.second += 1;
        else {
            preRecord.first = root->val;
            preRecord.second = 1;
        }
        // 若是当前的众数
        if (preRecord.second >= maxFreq) {
            if (preRecord.second > maxFreq) {
                maxFreq = preRecord.second;
                ans.resize(0);
            }
            ans.push_back(root->val);
        }
        if (root->right)
            findMode(ans, root->right, preRecord, maxFreq);
    }
};
posted @ 2022-05-09 20:33  MasterBean  阅读(198)  评论(0)    收藏  举报