栈与队列的最后一part
239. 滑动窗口最大值
我暂时能想到的是用队列做,然后写一个函数计算队列中最大元素。
但这样的话时间复杂度是不是会超(而且好像也用不上队列,直接计最大值就行)
要不要改成两个int,一个记录当前最大值,一个记录最大值位置?好像也不对。
进阶还要求线性的时间复杂度。。。
写了一个暴力的,果不其然超时了。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n=nums.size();
vector<int> result;
if(n==0) return result;//注意鲁棒性
for(int i=1;i<=(n-k);++i){
int m=INT_MIN;
for(int j=0;j<k;++k){
m=max(m,nums[j+i]);
}
result.push_back(m);
}
return result;
}
};
时间复杂度O(n*k)
不会写。。看看题解吧
(强烈建议看视频)
单调队列(要自己构造的一种队列)
需要实现的单调队列功能是什么样的呢?可以pop和push元素,而且还能随时返回当前队列中的最大值。
由于队列应该可以时刻返回最大值,所以应该是需要排序的队列,而且最大值还要始终在front位置才能实现随时返回。但滑动窗口还在不停移动,每一步都有旧元素pop掉和新元素push进,总不能每次都重新排列。
于是:
“
其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。
”
由此开始自己实现这个单调队列。
其中队列的front就是当前队列中最大的元素。
按照数组顺序依次push和pop元素入此单调队列
单调队列push的规则:
如果push进来的元素比前面都大,那么前面都要排除。因为
“没必要维护当前最大元素之前比它还小的元素”。
如果比前面的元素小,可以留存,但是如果当前入队列的元素,在这个队列前面有比它小的队列,都不需要留着统统踢走。
先放一个AC的
(有一个小细节需要确认)
class DEQUE{
public:
deque<int> que;
void POP(int x){
if(que.empty()) return;
if(que.front()!=x) return;
else{
que.pop_front();
}
}
void PUSH(int x){
if(que.empty()) {//如果队列是空的,直接放
que.push_back(x);
return;
}//队列非空,前面小于当前x的直接踢出
while(que.empty()==0 && que.back()<x){//?
que.pop_back();
}
que.push_back(x);
}
int get_max(){
return que.front();
}
};
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n=nums.size();
vector<int> result;
if(n==0) return result;
if(n<k){
sort(nums.begin(),nums.end());
result.push_back(nums[n-1]);
return result;
}
DEQUE q;
for(int i=0;i<k;++i){
q.PUSH(nums[i]);
}
for(int i=0;i<n-k;++i){
result.push_back(q.get_max());
q.POP(nums[i]);
q.PUSH(nums[i+k]);
}
result.push_back(q.get_max());
return result;
}
};
小于等于还是小于?为什么把等于的也都踢掉就不对?
结合测试用例来看,前面存在等于的就是不能踢出(到底是为什么呢我暂时也解释不出来 )(就当这里是面对测试编程好了 )(虽然给我点赞的都是机器人但是如果真有知道为啥的朋友可以在评论区告诉我吗)
来自卡哥题解的补充:
单调队列不是一成不变的,而是不同场景不同写法,总之要保证队列里单调递减或递增的原则,所以叫做单调队列。 不要以为本题中的单调队列实现就是固定的写法哈。
347.前 K 个高频元素
感觉暴力肯定也能做,统计每个元素的出现次数然后排序,输出排序前k个的次数对应的元素。
统计出现次数可以遍历一遍,每个元素对应次数存入哈希表。
进阶要求算法时间复杂度小于O(nlogn)
不会,看题解去了。
使用优先级队列,可以在队列中自动根据排序条件将push进来的元素排序。
而在STL中,优先级队列的底层原理是大根堆和小根堆。
大根堆是一种完全二叉树,其中每个根节点都不小于其左右孩子,小根堆就是每个根节点都不大于其左右孩子。
想要求前k个次数,只需要维护k个根节点的二叉树就可以,那么用大根堆还是小根堆?
(我一开始也想的是用大根堆)但是每次有元素入二叉树的时候,也有元素对应出队列,大根堆的堆顶就会对应被挤出队列(因为在队列最前面)但是大根堆的堆顶为最大元素,理应不被排出。所以得到小根堆,每次有更新就挤出最小的堆顶元素,堆里始终保留的就是前k个最大的元素。
可以先用map统计每个元素出现的次数对应存入<key,value>中,然后使用迭代器遍历此哈希表入小根堆。
其中:
*it:这是一个迭代器 it 指向的元素的解引用操作。迭代器 it 指向一个容器中的元素,通过解引用操作 *it 可以得到这个元素。
使用C++里封装好的优先级队列的类priority_queue来实现小根堆。
priority_queue默认实现的是大根堆,所以还需要自己定义比较器。
比较器是一个定义在当前队列中的嵌套类,类名是比较器名,类中的函数是重载运算符()。
在优先级队列中,优先级最高的元素应该在队列的前端。
因此可以把小的数看作是优先级高的元素。return lhs.second>rhs.second
尝试写一下:
class Solution {
public:
class mycompare {
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) {
int n = nums.size();
vector<int> result;
if (n == 0)
return result; // 但是由于K总是合理的,其实这句话没什么用处
unordered_map<int, int> myMap;
for (int i = 0; i < n; ++i) {
myMap[nums[i]]++;
}
priority_queue<pair<int, int>, vector<pair<int, int>>, mycompare>
myqueue;
for (unordered_map<int, int>::iterator it = myMap.begin();
it != myMap.end(); it++) {
myqueue.push(*it);
if (myqueue.size() > k) {
myqueue.pop();
}
}
for (int i = 0; i < k; ++i) {
result.push_back(myqueue.top().first);
myqueue.pop();
}
return result;
}
};
半抄半改的写完了。。。感觉是一道需要狠狠二刷的题
比较器,迭代器,优先队列,pair等等都是第一次用。
至此,栈与队列part暂时完结了(草草地 )
代码随想录网站给出的面试题:栈里面的元素在内存中是连续分布的么?
否。
第一,栈是容器适配器,底层容器使用不同的容器,导致栈中的内容在内存中不一定是连续的。
第二,缺省情况下,默认使用deque实现栈,deque不是连续的。
又写了一下第71题:
71 简化路径
先贴一下代码
class Solution {
public:
string simplifyPath(string& path) {
path+='/';
string result="/";
deque<string> cache;
//cache.push("/");//第一个必须以/开头
int n=path.size();
string curr="";
for(int i=0;i<n;++i){
if(path[i]=='/'){
if(curr=="") continue;
else if(curr==".") {
curr="";
continue;
}
else if(curr==".."){
if(cache.empty()==0){
cache.pop_back();
}
curr="";
}
else{
cache.push_back(curr);
curr="";
}
}
else{
curr+=path[i];
}
}
//if(curr!="") cache.push_back(curr);
while(cache.empty()!=1){
result+=cache.front();
if(cache.size()>1){
result+='/';
}
cache.pop_front();
}
return result;
}
};
一开始用栈做的,输出到string result中发现顺序不对,于是打算改双边队列做。
双边队列倒是可以实现,但是有一个小问题在于这个路径可能书写规范也可能不规范,在我的判断条件中只有遇到Path出现‘/’时才会对curr字符串进行操作(或将curr入队列或从队列中弹出一个元素)但是如果他书写规范,可能最后一位没有‘/’,就会差一个curr没被操作。
于是我索性修改输入的path,将他们最后统一添加一个‘/’,完美解决。
灵魂四问:
- C++中stack,queue 是容器么?
- 我们使用的stack,queue是属于那个版本的STL?
- 我们使用的STL中stack,queue是如何实现的?
- stack,queue 提供迭代器来遍历空间么?
栈和队列被称为容器适配器,而非容器。它们是由底层容器(vector,deque,list)来实现的。具有”可插拔性“就是可以自由选择用那种底层容器来实现。默认情况下都是用deque来实现的。不提供迭代器来遍历空间。