滑动窗口最大值
文档讲解:代码随想录
视频讲解: 单调队列正式登场!| LeetCode:239. 滑动窗口最大值
状态: ×
本题的思想就是利用一个单调队列来存储可能成为最大值的元素,单调队列的意思就是一个队列从出口元素到末尾元素是单调递增或者单调递减的。就拿这道题来说
按数组顺序向队列压入元素,如果push的元素比队列中的元素大,那么就pop掉所有比这个元素小的元素,直到队列中的元素比加入的元素大为止(pop_back())。
那么pop一个元素除了上面那种情况,还有就是滑动窗口在滑动的过程中,左边界已经超过了队列的出口元素,这时候就需要将出口元素pop掉。
//自定义一个单调队列,其队列中元素是单调递增的
class XQueue
{
private:
deque<int> dque;
public:
//基础弹出操作
//当vector中的窗口左边界需要向前移动时,判断当前元素是否与队列的出口元素相等
void pop(int value)
{
if(!dque.empty()&&value == dque.front())
{
dque.pop_front();
}
}
//压入操作
//处理掉队列中所有比value小的元素,如果没有则压入队尾,如果有则从队尾pop掉,直到队列中元素比value大为止
void push(int value)
{
while(!dque.empty()&&dque.back()<value)
{
dque.pop_back();
}
dque.push_back(value);
}
//获取最大值,即队列的出口元素
//每轮滑动窗口都获取一次
int GetMax()
{
return dque.front();
}
};
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
XQueue dque;
//结果数组
vector<int> res;
//初始化dque,nums前k个数据
for(int i=0;i<k;i++)
{
dque.push(nums[i]);
}
//添加第一个最大值
res.push_back(dque.GetMax());
//滑动窗口
for(int i = k;i<nums.size();i++)
{
//如果nums[i-k] = dque.front;相当于nums[i-k]已经不在窗口中那么需要pop
dque.pop(nums[i-k]);
//压入
dque.push(nums[i]);
//添加最大值
res.push_back(dque.GetMax());
}
return res;
}
};
时间复杂度:除了最后一组最大值之后的元素,每个元素被push和pop了一次, O ( 2 n − k ) = O ( n ) O(2n-k) = O(n) O(2n−k)=O(n)
前K个高频元素
文档讲解:代码随想录
视频讲解: 优先级队列正式登场!大顶堆、小顶堆该怎么用?| LeetCode:347.前 K 个高频元素
状态:×
利用优先级队列排序,其本质就是一个大顶堆或者小顶堆,而这道题要求我们计算频率最高的k个数,应当采用小顶堆,也就就是说当当前的k个数满了之后,要弹出最小值。
优先级队列和堆
class Solution {
public:
class mycomparison {
public:
bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
return lhs.second > rhs.second;
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> numsmap;
for(int num : nums)
{
if(numsmap.find(num)!=numsmap.end())
{
numsmap.find(num)->second++;
}
else
{
numsmap.insert(pair<int,int>(num,0));
}
}
//优先级队列 -- 小顶堆
priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
//将Map的值push队列中会自动按照mycomparison排序
for(unordered_map<int,int>::iterator it = numsmap.begin();it!=numsmap.end();it++)
{
pri_que.push(*it);
if(pri_que.size()>k)
{
pri_que.pop();
}
}
vector<int> res;
for(int i = 0;i<k;i++)
{
res.push_back(pri_que.top().first);
pri_que.pop();
}
return res;
}
};
总结
文档讲解:代码随想录
视频讲解:
状态
栈的应用:在需要顺序匹配且匹配情况会影响下一轮结果的情况下可以考虑使用栈,先入后出的特性使得可以处理当前下标元素与该下标之前k个元素之间的关系。
比如像有效的括号这道题,匹配的情况就是i+1与i的匹配,同时还需要考虑 i+2与i-1的匹配,通过栈实现存储待匹配队列的左括号,下标大的就需要先匹配,就是一个典型的先进后出的应用。同样道理如同删除字符串中的所有相邻重复项,重复的删除后原本不相邻的元素会相邻,这时候使用栈可以是实现存储不重复的字符,因为一旦重复,那么就会从栈里弹出,这样就可以暴露先进栈的元素。
还有对于比较含退格的字符串,也可以使用栈。
对于使用栈的题都可以使用同向双指针解法来解决,比如删除元素,那么在匹配的情况下,左指针就需要进行退格操作了,而不是原地不动。
c++中的stack和queue不是容器,而是由容器构成的一种数据结构,可以叫做容器适配器
其底层实现可以是list vector deque甚至是链表
缺省的状态下是deque,栈和队列的内存分布取决于其底层容器的内存分布,deque的内存分布是不连续的
优先级队列:其默认底层容器为vector,本质就是一个由vector容器构成的大顶堆。会对加入的数据排序。
创建优先级队列是可以自定义排序规则。
template <typename T,typename Container=std::vector<T>,typename Compare=std::less<T> >
class priority_queue{
//......
}