239. 滑动窗口最大值
题目链接:滑动窗口最大值
视频讲解:单调队列正式登场!
对于此题我们要自己实现一个单调队列,对头为最大值。那么怎么设计呢?主要是对于队列的push和pop合理设计,即能实现它的单调性。
其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。所以单调队列不仅仅是对窗口里的值进行排序。
pop和push的规则:
1、push:如果push的元素大于队尾的元素,那么就将队尾元素弹出,直到push的元素小于等于队尾的元素为止;
2、pop:如果需要移除的元素等于队头的元素(大于的情况push中已经处理了),那么队列弹出元素,否则不进行操作。
保证上述规则,每次窗口移动时,队头的元素就一直是当前窗口的最大值。
// 时间复杂度: O(n)
// 空间复杂度: O(k) 空间复杂度因为我们定义一个辅助队列,所以是O(k)
class Solution {
public:
class MyQueue // 单调队列
{
public:
deque<int> que; // 用duque实现单调序列
void push(int value)
{
while (!que.empty() && value > que.back())
{
que.pop_back();
}
que.push_back(value);
}
void pop(int value)
{
if (!que.empty() && value == que.front()) // que的front是里面最大的元素,pop_front要满足value >= que.front,>的情况再push中处理了,pop中只用处理相等的情况
{
que.pop_front();
}
}
int front()
{
return que.front();
}
};
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
MyQueue que;
vector<int> res;
for (int i = 0; i < k; ++i)
{
que.push(nums[i]);
}
res.push_back(que.front()); // 单调队列头元素放入结果数组中
for (int i = k; i < nums.size(); ++i)
{
que.pop(nums[i - k]);
que.push(nums[i]);
res.push_back(que.front());
}
return res;
}
};
347.前 K 个高频元素
题目链接:前 K 个高频元素
视频讲解:优先级队列正式登场!
根据题意,我们需先统计元素出现的频率,显然用map比较方便。然后就是对频率进行排序取出前k大的,这就引出了优先级队列(是个堆)。
定义优先级队列
1、使用默认比较函数:
std::priority_queue<int> pq;
创建了一个存储整数类型的优先级队列,底层容器使用默认的std::vector<int>
,默认使用 < 运算符进行比较,是由大到小。
2、使用自定义比较函数或函数对象:
struct Compare {
bool operator()(int a, int b) {
// 自定义的比较逻辑
// 返回 true 表示 a 的优先级高于 b
// 返回 false 表示 a 的优先级低于或等于 b
}
};
std::priority_queue<int, std::vector<int>, Compare> pq;
这种方式创建了一个存储整数类型的优先级队列,底层容器使用 std::vector<int>
,并使用自定义的 Compare
结构体或类来定义元素的优先级比较方式。Compare
需要重载 ()
运算符来实现比较逻辑。
3、使用lambda表达式:
auto comp = [](int a, int b) {
// 自定义的比较逻辑
// 返回 true 表示 a 的优先级高于 b
// 返回 false 表示 a 的优先级低于或等于 b
};
std::priority_queue<int, std::vector<int>, decltype(comp)> pq(comp);
这种方式创建了一个存储整数类型的优先级队列,底层容器使用 std::vector<int>
,并使用lambda 表达式来定义元素的优先级比较方式。decltype(comp)
用于推导 lambda 表达式的类型,并将其作为模板参数传递给 std::priority_queue
。
注意:题目要求前k个高频元素,不要直接想着就用大顶堆了,大顶堆把最大的值放在最上面,也是最先弹出去的,那就找不到前k个了,所以要用小顶堆。保证优先级队列大小为k,这样每次弹出去的都是较小的,留到最后的k个值就是所求结果。
// 时间复杂度: O(nlogk)
// 空间复杂度: O(n)
class Solution {
public:
struct mycomparison{
bool operator() (const pair<int, int>& l, const pair<int, int>& r)
{
return l.second > r.second; // 比较第二个值,出现的次数 左大于右是小顶堆
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> map; // key为数组里的数,value为出现的次数
for (int i = 0; i < nums.size(); ++i)
{
map[nums[i]]++;
}
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pq; // 优先级队列
for (auto it = map.begin(); it != map.end(); ++it)
{
pq.push(*it); // map放入队列中
if (pq.size() > k) // pq大小定为k,超过k弹出
{
pq.pop();
}
}
vector<int> res(k); // 别忘初始化大小,存放结果
for (int i = k - 1; i >= 0; --i) // 由大到小存储,在优先级队列中是由小到大的
{
res[i] = pq.top().first; // 存放key,对应数组中的元素
pq.pop();
}
return res;
}
};