代码手记笔录——优先队列与单调队列
优先队列
优先队列采用堆结构存储数据,高优先级大顶堆。 可以重写比较器 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);
}
};


浙公网安备 33010602011771号