基础知识:
栈与队列都是容器接口,而非容器;
栈与队列可选容器,缺省状态下是deque;
提供push,pop等操作,但不提供送代器,不提供走访功能,因为只能在一边进行插入,弹出操作;
- 栈的经典题目
1.栈在系统方面有许多应用
Linux命令 cd/a/b/....运用栈的知识,确认进入了a目录;
函数递归必须要在栈中进行
2.匹配问题
例如,leetcode20,有效的括号
class Solution {
public:
bool isValid(string s) {
stack<int> tmp;
for (int i=0;i<s.size();i++) //此题利用栈进行匹配,若全部匹配成功,最后栈为空,重点要考虑有哪些不匹配的情况,共有三种
{
if (s[i]=='(')
tmp.push(')');
else if(s[i]=='[')
tmp.push(']');
else if(s[i]=='{')
tmp.push('}');
else if(tmp.empty()) //1.s没遍历玩,栈就空了,说明右边多了;
return false;
else if(tmp.top()!=s[i]) //2.匹配错误
return false;
else
tmp.pop();
}
return tmp.empty(); //3.遍历完栈内还有元素,说明push的多余pop的,说明左边多了。
}
};
例如1047,删除字符串中所有相邻重复项
class Solution {
public:
string removeDuplicates(string s) {
stack<char> exm;
exm.push(s[s.size()-1]);
for (int i=s.size()-2;i>=0;i--)
{
if(exm.empty() || s[i]!=exm.top()) //开始时没写exm.empty(),出现aaa等情况便出错
{
exm.push(s[i]);
continue;
}
exm.pop();
}
/*if(exm.empty())
return NULL;*/ //此处不需要这一判断,因为下面可以覆盖这种情况
int size=exm.size();
//s.resize(size);
string sq;
for (int i=0;i<size;i++)
{
//s[i]=exm.top();
sq.push_back(exm.top());
exm.pop();
}
return sq;
}
};
150,逆波兰表达式求值,此题思路一样,,另外有点消消乐的感觉,实现代码要比前两道还简单,关键stoi函数的使用之前不清楚
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> numbers;
for(int i = 0 ; i < tokens.size() ;++i){
if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/"){
int res;
int n2 = numbers.top();
numbers.pop();
int n1 = numbers.top();
numbers.pop();
if(tokens[i] == "+")
res = n1 + n2;
else if(tokens[i] == "-")
res = n1 - n2;
else if(tokens[i] == "/")
res = n1 / n2;
else
res = n1 * n2;
numbers.push(res);
}else{
numbers.push(stoi(tokens[i])); //注意stoi函数
}
}
return numbers.top();
}
};
- 队列经典题目
队列方面遇到了两种值得细究的结构:单调队列,优先级队列
看题239,滑动窗口最大值
(主要思想是队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。
那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。C++中没有直接支持单调队列,需要我们自己来一个单调队列
而且不要以为实现的单调队列就是 对窗口里面的数进行排序,如果排序的话,那和优先级队列又有什么区别了呢。)
-carl
class Solution {
class mypri //把pop,push等操作的条件判断放在mypri的构建中
{
public:
deque<int> t;
void pop(int value)
{
if(!t.empty() && value==t.front()) //pop的两个判断,前一个一般都要写
t.pop_front();
}
void push(int value)
{
while(!t.empty() && value>t.back())
{
t.pop_back();
}
t.push_back(value);
}
int front()
{
return t.front();
}
};
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> result;
mypri test;
for (int i=0;i<k;i++) //本题要求每一个k组最大值,所以要先有第一个k组后
{ //再进行流程化操作;
test.push(nums[i]);
}
result.push_back(test.front());
for (int i=k;i<nums.size();i++)
{
test.pop(nums[i-k]);
test.push(nums[i]);
result.push_back(test.front());
}
return result;
}
};
用deque作为单调队列的底层数据结构,C++中deque是stack和queue默认的底层实现容器(这个我们之前已经讲过),deque是可以两边扩展的,而且deque里元素并不是严格的连续分布的。 -carl
347.前k个高频元素
本题主要有以下几点
1.用unordered_map存放元素及出现的频率
2.用优先级队列实现小顶堆
另外还要注意一下如比较函数等细节
class Solution {
public:
class mycomparison
{
public:
bool operator()(const pair<int,int>& left,const pair<int,int>& right)
{
return left.second>right.second; //与快排函数的比较相反
}
};
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> t;
for (int i=0;i<nums.size();i++)
{
t[nums[i]]++;
}
priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparison> p; //刚接触,pair<>也是一种数据类型
for (unordered_map<int,int>::iterator it=t.begin();it!=t.end();it++)
{
p.push(*it);
if(p.size()>k)
p.pop();
}
vector<int> result(k); //此处用reserve失效!!!,应该是系统用送代器去打印元素
// result.reserve(k);
for (int i=k-1;i>-1;i--)
{
result[i]=p.top().first;
p.pop();
}
return result;
}
};
附上代码随想录中对于这一部分的描述:
什么是优先级队列呢?
其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。
而且优先级队列内部元素是自动依照元素的权值排列。那么它是如何有序排列的呢?
缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。
什么是堆呢?
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
所以大家经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接用priority_queue(优先级队列)就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。
本题我们就要使用优先级队列来对部分频率进行排序。
为什么不用快排呢, 使用快排要将map转换为vector的结构,然后对整个数组进行排序, 而这种场景下,我们其实只需要维护k个有序的序列就可以了,所以使用优先级队列是最优的。
-carl