【算法竞赛进阶指南】0x11 栈

文章介绍了如何使用栈数据结构维护序列中的最小元素,包括使用两个栈来处理修改操作,以及在后缀表达式求值中的应用。还涉及到了单调栈和验证栈序列的题目解决方案。
摘要由CSDN通过智能技术生成

算法竞赛进阶指南_李煜东_17588649_zhelper-search.pdf

索引:59

0x11 栈

基础应用

两个栈维护前面区间的最小元素

41. 包含min函数的栈 - AcWing题库

可以开个小根堆维护最小值,但是这样时间是logN的。如果只用一个变量来存,一旦出现了出栈操作就无从下手了。因此用一个线性结构来保存每个历史时刻的最小值。

开两个栈,一个用来存数据,一个记录当前最小值。

有数据入栈,就把这个数据与minn栈栈顶做对比决定是否入栈。出栈也做个对比即可维护这样一个数据结构。

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> st;
    stack<int> minn;
    MinStack() {
        
    }
    
    void push(int x) {
        st.push(x);
        if(minn.empty()||minn.top()>=x) minn.push(x);
    }
    
    void pop() {
        if(st.top()==minn.top()) minn.pop();
        st.pop();
    }
    
    int top() {
        return st.top();
    }
    
    int getMin() {
        return minn.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

序列中修改某个指定位置

始终在序列中间,某个指定位置进行修改,就可以用两个栈,类似于对顶堆用对顶栈来维护。

P2201 数列编辑器 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

128. 编辑器 - AcWing题库

#include<bits/stdc++.h>
using ll=long long;
#define int ll
const int N=1e6+10;
const int M=1e5+10;
int q;
int s[N],m[N];
std::stack<int> l,r;
void solve()
{
	std::cin>>q;
	m[0]=-2e9;
	while(q--)
	{
		char op;
		std::cin>>op;
		//getchar();
		int x;
		if(op=='I')
		{
			std::cin>>x;
			l.push(x);
			s[l.size()]=s[l.size()-1]+x;
			m[l.size()]=std::max(m[l.size()-1],s[l.size()]);
			
		}else if(op=='D'&&l.size()){
			l.pop();
			
		}else if(op=='L'&&l.size()){
			r.push(l.top());
			l.pop();
			
		}else if(op=='R'&&r.size()){//光标右移 
			x=r.top();
			r.pop();
			l.push(x);
			
			s[l.size()]=s[l.size()-1]+x;
			m[l.size()]=std::max(m[l.size()-1],s[l.size()]);
		}else if(op=='Q'){
			
			std::cin>>x;
			std::cout<<m[x]<<'\n';
		}
	}
}
signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	int t=1;
	//std::cin>>t;
	while(t--)
	{
		solve();
	}
	return 0;
} 

值得注意的是开了ios之后就不要用getchar()这类c的输入了。

入栈顺序一定,求可能的出栈情况(数学题,后面补)

129. 火车进栈 - AcWing题库

dfs枚举O(2^{n})

这题要求输出每种可能的出栈顺序,因为这里n<20。

dfs枚举,对于每一个状态,我们只有两种选择:

1入栈

2出栈

#include<bits/stdc++.h>
using ll=long long;
const int N=2e5+10;
int n;
std::vector<int> ans; 
std::stack<int> st;
int cnt;
void dfs(int x)
{
	if(cnt>=20) return ;
	if(ans.size()>=n)//全都出栈了 
	{
		cnt++;
		for(auto i:ans) std::cout<<i;
		std::cout<<'\n';
		return ;
	}
	if(st.size())//如果栈里还有元素 
	{//考虑出栈 
		ans.push_back(st.top());
		st.pop();
		dfs(x);
		st.push(ans.back());
		ans.pop_back();	
	}	
	if(x<=n)//还能入栈 
	{
		st.push(x);
		dfs(x+1);
		st.pop();
	}
} 
void solve()
{
	std::cin>>n;
	dfs(1);
}
signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	int t=1;
	//std::cin>>t;
	while(t--)
	{
		solve();
	}
	return 0;
} 
 递推O(n)

130. 火车进出栈问题 - AcWing题库

这个问题只关心方案数并不关心具体的方案。数据是60000,暴力枚举肯定是不行的。

表达式求值

后缀表达式求值

1.如果遇到一个数就把这个数入栈

2.遇到运算符就弹出栈中两个元素进行运算,把结果入栈

3.扫描完成后,栈中只剩一个数,就是表达式的值

中缀表达式求值

如果想让计算机求解中缀表达式,最快的方法就是把中缀表达式转化成后缀表达式。这个转化过程可以用栈O(N)完成。

1.建立一个存运算符的栈,逐一扫描中缀表达式的元素。

        1)遇到一个数就输出

        2)左括号入栈

        3)右括号,不断出栈并输出直到栈顶为左括号,然后左括号出栈。

        4)遇到运算符,只要栈顶符号优先级不低于新符号就不断出栈,最后把新符号入栈。乘除>加减>左括号。

 2.依次取出并输出栈中所有剩余符号,最终输出的序列就是一个与原中缀表达式等价的后缀表达式。

 也可以递归直接求解,时间复杂度O(N)。

中缀表达式的递归法求值

求解中缀表达式s[1~N]的值

1.在L~R中考虑没有被任何括号包含的运算符

1)若存在加减号,选最后一个,分成左右两半递归,结果相加减,返回。

2)若存在乘除号,选最后一个,分成左右两半递归,结果相乘除,返回。

2.所有运算符都被括号包含

1)首尾字符是括号,递归求解S[L+1~R-1],把结果返回

2)否则,说明区间S[L~R]是一个数,直接返回数值

单调栈

131. 直方图中最大的矩形 - AcWing题库

HISTOGRA - Largest Rectangle in a Histogram - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

因为最大矩形只与高和宽有关系,因此我们可以对每个点,求出以它为高的最小左边界和最大右边界。那么这个问题就变成了,找出左边第一个比它小的坐标,右边第一个比它小的坐标。最后枚举每个点,计算面积枚举最大值即可。

找到左边第一个小/大,这是一个经典的单调栈问题。我们对左右两边各做一次就好。

#include<bits/stdc++.h>
using ll=long long;
const int N=1e5+10;
int n;
int h[N],l[N],r[N];
std::stack<int> sl,sr;
ll ans=-1;
void solve()
{
	while(std::cin>>n&&n!=0) 
	{
		for(int i=1;i<=n;i++)	std::cin>>h[i];
		
		h[0]=-1,h[n+1]=-1;
		
		//找左边第一个小的
		sl.push(0);//存下标 
		for(int i=1;i<=n;i++)
		{
			while(sl.size()&&h[sl.top()]>=h[i]) sl.pop();
			l[i]=sl.top();
			sl.push(i);	
		} 
		
		sr.push(n+1);
		for(int i=n;i>=1;i--)
		{
			while(sr.size()&&h[sr.top()]>=h[i]) sr.pop();
			r[i]=sr.top();
			sr.push(i);	
		} 
		
		ans=0;
		for(int i=1;i<=n;i++)
		{
			ans=std::max(ans,(ll)h[i]*(r[i]-l[i]-1));
		}
		std::cout<<ans<<'\n';
	}
}
signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	int t=1;
	//std::cin>>t;
	while(t--)
	{
		solve();
	}
	return 0;
} 

练习

给定入栈顺序,判定出栈顺序是否合法

946. 验证栈序列 - 力扣(LeetCode)

LCR 148. 验证图书取出顺序 - 力扣(LeetCode)这两题完全一样。

对每个应该入栈的元素入栈,同时比较栈顶元素和当前应该出栈的元素是否相同,相同就出栈。如果最后栈空说明可以,不空就不行。

class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        std::stack<int> st;
        int j=0;
        for(auto i:pushed)
        {
            st.push(i);
            while(st.size()&&st.top()==popped[j])
            {
                st.pop();
                j++;
            }
        }
        if(st.size()) return false;
        else return true;
    }
};

给定入栈顺序,求出字典序最大的出栈顺序

A-栈和排序_2021秋季算法入门班第四章习题:堆栈队列单调栈等 (nowcoder.com)

思想就是比较当前栈顶元素比后面待入栈的元素都要大了,就要输出,否则我们就可以等到后面入栈的时候再输出。

#include<bits/stdc++.h>
using ll=long long;
const int N=1e6+10;
int n;
int a[N],m[N];
std::stack<int> st;
void solve()
{
	int n;
	std::cin>>n;
	for(int i=1;i<=n;i++)
	{
		std::cin>>a[i];
	}
	//求出后缀最大值
	for(int i=n;i>=1;i--)
	{
		m[i]=std::max(m[i+1],a[i]);
	} 
	for(int i=1;i<=n;i++)
	{
		st.push(a[i]);
		while(st.size()&&st.top()>m[i+1])
		{
			std::cout<<st.top()<<" ";
			st.pop();
		}
	}
	while(st.size())
	{
		std::cout<<st.top()<<" ";
		st.pop();
	}
}
signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	int t=1;
	//std::cin>>t;
	while(t--)
	{
		solve();
	}
	return 0;
} 

给定后缀表达式计算结果

B-牛牛与后缀表达式_2021秋季算法入门班第四章习题:堆栈队列单调栈等 (nowcoder.com)

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 给定一个后缀表达式,返回它的结果
     * @param str string字符串 
     * @return long长整型
     */
    stack<long long> st;
    long long legalExp(string str) {
        // write code here
        int flag=0;
        long long num=0;
        for(auto i:str)
        {
            if(i>='0'&&i<='9')
            {
                num=num*10+i-'0';
            }else if(i=='#'){
                st.push(num);
                num=0;
            }else{//运算符
                long long a=st.top();
                st.pop();
                long long b=st.top();
                st.pop();
                if(i=='+') num=a+b;
                else if(i=='-') num=b-a;
                else num=a*b;
                st.push(num);
                num=0;
            }
        }
        return st.top();
    }
};

判断一个字符串能否由“ab“不停插入得到

C-好串_2021秋季算法入门班第四章习题:堆栈队列单调栈等 (nowcoder.com)

类似于括号匹配问题,可以把a看作左括号,b看作右括号。每遇到一个b就去弹出一个a。

#include<bits/stdc++.h>
std::string s;
std::stack<char> st;
void solve()
{
    std::cin>>s;
    for(auto i:s)
    {
        if(i=='a') st.push(i);
        else{
            if(st.empty()||st.top()!='a') 
            {
                std::cout<<"Bad"<<'\n';
                return ;
            }
            st.pop();
        }
    }
    if(st.size()) std::cout<<"Bad"<<'\n';
    else std::cout<<"Good"<<'\n';
}
signed main()
{
    int t=1;
    //std::cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值