目录
Day10
1 理论基础
2 刷题
2.1 Leetcode232 用栈实现队列
2.2 Leetcode225 用队列实现栈
2.3 leetcode20 有效的括号
2.4 Leetcode1047. 删除字符串中的所有相邻重复项
Day 11
2.5 Leetcode 150. 逆波兰表达式求值
2.7 Leetcode347 前k个高频元素
栈和队列是STL(C++标准库)里面的两个数据结构。
栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。
栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。
所以STL中栈往往不被归类为容器,而被归类为container adapter(容器适配器)。
栈的内部结构,栈的底层实现可以是vector,deque,list 都是可以的, 主要就是数组和链表的底层实现。
我们常用的SGI STL,如果没有指定底层实现的话,默认是以deque为缺省情况下栈的低层结构。
deque是一个双向队列,只要封住一段,只开通另一端就可以实现栈的逻辑了。
SGI STL中 队列底层实现缺省情况下一样使用deque实现的。
STL 队列也不被归类为容器,而被归类为container adapter( 容器适配器)。
力扣
题干:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
代码:注意pop( ) 函数的实现
class MyQueue {
public:
stack<int> stack1;//这个栈模拟队列的压入
stack<int> stack2;//这个栈模拟队列的压出,先进先出
MyQueue() {
}
void push(int x) {
stack1.push(x);
}
int pop() {
if(stack2.empty())//stack2为空,把stack1的全倒过来
{
while(!stack1.empty())//当stack1压入的元素个数不为0时,全转移到stack2中
{
stack2.push(stack1.top());//为了保证先进先出,那么stack2需要把stack1压底的放在最上面
stack1.pop();
}
}
//如果stack2一开始不为空,或者为空但经历了上面的if后不空了,直接弹出
int ret=stack2.top();
stack2.pop();
return ret;
}
int peek() {
int ret=this->pop();//借用pop函数,把stack2弄好初始化
stack2.push(ret);//pop函数把ret去除了,重新加入
return stack2.top();
}
bool empty() {
return stack1.empty()&&stack2.empty();//两个都要判断,可能存在push后没做其他操作了,这说明stack1不空,还没来得及到stack2
}
};
力扣
方法1:用两个队列实现栈。
队列是先进先出的规则,把一个队列中的数据导入另一个队列中,数据的顺序并没有变,并没有变成先进后出的顺序。
用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用,把que1最后面元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。
//方法1:注意pop( )函数的实现,两个队列实现;建议画图模拟下
class MyStack {
public:
queue<int> Q1;
queue<int> Q2;//备份用,留下Q1的最后一个元素,把其他元素备份到Q2,然后再还回去
MyStack() {
}
void push(int x) {
Q1.push(x);
}
int pop() {
int size=Q1.size();
size=size-1;//减去1,把要出去的那个元素(即队列中)留下
while(size)//当size不为0时循环,即把Q1的元素全部备份到Q2,(当然除了最后一个元素)
{
Q2.push(Q1.front());
Q1.pop();
size--;
}
int ret=Q1.front();//还剩下一个元素,比如 1 2 3,还剩一个3,符合栈上先进后出的规律
Q1.pop();
while(!Q2.empty())
{
Q1.push(Q2.front());
Q2.pop();
}
return ret;
}
int top() {
return Q1.back();//栈的栈顶元素,即为队列的最后一个元素
}
bool empty() {
return Q1.empty();
}
};
方法2:用一个队列实现栈
一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。
//方法2:用一个队列实现栈的功能,注意pop( )函数的实现
class MyStack {
public:
queue<int> Q1;
MyStack() {
}
void push(int x) {
Q1.push(x);
}
//循环入队,留下最后一个元素。把Q1前面的元素再次入队,这样位置到了后面(当然入队的同时,把它们原有的位置出队)
int pop() {
int size=Q1.size();
size=size-1;
while(size--)
{
Q1.push(Q1.front());
Q1.pop();
}
int ret=Q1.front();//最后面的那个元素剩下了,此时成为了最前面了
Q1.pop();
return ret;
}
int top() {
return Q1.back();
}
bool empty() {
return Q1.empty();
}
};
力扣
题目:给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
思路:
括号匹配是使用栈解决的经典问题。
先来分析一下 这里有三种不匹配的情况:
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false;
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false;
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false;
那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。
还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
代码:
//代码随想录题解
bool isValid(string s) {
if(s.size()%2!=0)//如果字符串不是偶数
{
return false;
}
stack<char> stack1;
for(int i=0;i<s.size();i++)
{
if(s[i]=='(')
{
stack1.push(')');
}
else if(s[i]=='[')
{
stack1.push(']');
}
else if(s[i]=='{')
{
stack1.push('}');
}
//第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号 return false
//第二种情况:遍历字符串匹配的过程中,发现栈里没有我们要匹配的字符。所以return false
else if(stack1.empty()||stack1.top()!=s[i])//判断empty必须在前面,也包含开始就是右括号,此时栈为空
{
return false;
}
else//如果符合条件,把这个符号去掉
{
stack1.pop();
}
}
//第一种情况:此时我们已经遍历完了字符串,判断栈是否为空,如果栈不为空,说明有相应的左括号没有右括号来匹配,所以return false,否则就return true
return stack1.empty();
}
力扣
匹配问题都是栈的强项。
思路:本题要删除相邻相同元素,相对于20. 有效的括号 来说其实也是匹配问题,20. 有效的括号 是匹配左右括号,本题是匹配相邻元素,最后都是做消除的操作。
我们在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,我们在前一位是不是遍历过一样数值的元素,那么如何记录前面遍历过的元素呢?
所以就是用栈来存放,那么栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。
然后再去做对应的消除操作。
注意:最后从栈里弹出的元素是倒序的,所以在对字符串进行反转一下,就得到了最终的结果。
代码:
string removeDuplicates(string s) {
stack<char> stack1;
for(int i=0;i<s.size();i++)//遍历字符串
{
//此时还要保证栈不为空,否则空栈不能求top函数
if(!stack1.empty()&&stack1.top()==s[i])//如果栈顶元素(即刚入栈的元素)和下一个元素相等,则可以消消乐,即新元素不入栈,栈顶元素出栈
{
stack1.pop();
}
else //栈为空或者不相等则入栈,if和else对应
{
stack1.push(s[i]);
}
}
string str="";
while(!stack1.empty())
{
str+=stack1.top();
stack1.pop();//记得弹出,老忘!!!
}
reverse(str.begin(),str.end());//已经考虑到了stack1为空的情况,此时还是空字符串
return str;
}
力扣
题目:根据 逆波兰表示法,求表达式的值。有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。
适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
代码:
// 栈的经典应用
//如果遇到操作数,则将数入栈;
//如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈。
int evalRPN(vector<string>& tokens) {
stack<int> st;
for(int i=0;i<tokens.size();i++)
{
if(tokens[i]=="+"||tokens[i]=="-"||tokens[i]=="*"||tokens[i]=="/")
{
int temp1=st.top();
st.pop();
int temp2=st.top();
st.pop();//把离栈顶最近的两个元素取到后弹出
if(tokens[i]=="+")
{
st.push(temp2+temp1);//注意是前面的那个数+ - * /后一个数
}
if(tokens[i]=="-")
{
st.push(temp2-temp1);
}
if(tokens[i]=="*")
{
st.push((long long)temp2*(long long)temp1);//注意这里int溢出了
}
if(tokens[i]=="/")
{
st.push(temp2/temp1);
}
}
else
{
st.push(stoi(tokens[i]));//注意stoi()函数的使用,把string转换为int数据
}
}
return st.top();
}
2.6 Leetcode239 滑动窗口最大值
待做。
力扣
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
思路:
这道题目主要涉及到如下三块内容:
优先级队列:
其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。
而且优先级队列内部元素是自动依照元素的权值排列。那么它是如何有序排列的呢?
缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。
堆:
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
所以大家经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接用priority_queue(优先级队列)就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。
这里我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。
代码:
//堆建立时的规则
class mycmp{
public:
bool operator()(const pair<int,int> &left,const pair<int,int> &right)
{
return left.second>right.second;//注意这里与以前不一样,这里建立小顶堆,要return大于,按照次数进行排序(对map数据排序)
}
};
//方法:使用小顶堆,求大数取小顶堆,求小数用大顶堆
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int,int> mp;//用哈希表统计各个元素的次数(频率)
for(int num:nums)
{
mp[num]++;
}
//建立小顶堆,对map数据按照频率排序
//定义一个小顶堆,大小为k
priority_queue
for(auto iter=mp.begin();iter!=mp.end();iter++)
{
priQ.push(*iter);
if(priQ.size()>k)
{
priQ.pop();//把堆上最小的元素去掉(即堆顶)
}
}
//找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> v_ret(k,0);
for(int i=k-1;i>=0;i--)
{
v_ret[i]=priQ.top().first;
priQ.pop();
}
return v_ret; }