分类
链栈 & 顺序栈 普通栈 & 单调栈 满栈 & 空栈
单调栈
单调栈是一种拥有特殊的栈性质的数据结构,分为单调递减栈和单调递增栈两种类型, 单调递增栈:单调递增栈就是从栈底到栈顶数据是从大到小 单调递减栈:单调递减栈就是从栈底到栈顶数据是从小到大 单调栈的一般压栈弹栈逻辑: 以单调递减栈为例,当栈为空时直接进行压栈,当单调栈不为空时,为保证单调栈的单调性不变,当压入值小于栈顶元素时需先对栈顶元素进行弹栈操作之后再进行压栈。 逻辑作用: 平时使用时单调栈一般可用于数组内一组连续数据相关关系的判定,单调递增栈可用于查找数组内特定元素之后首个大于该元素数值的元素位置,单调递减栈则可找到首个小于该元素数值的位置。
满栈 & 空栈
满栈:压栈时先移动指针再存储数据,弹栈时先提取数据再移动指针
空栈:压栈是先存储数据再移动指针,弹栈时先移动指针再提取数据
栈的主要应用
1.反转序列
进制转换 & 单链表的翻转
2.连续子串的匹配
括号有效性检验,最长有效括号查找,排队看人头问题,柱状图中的最大矩形,中缀表达式求值
1)括号有效性检验:
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。
链接:https://leetcode-cn.com/problems/valid-parentheses
char pairs(char a) {
if (a == '}') return '{';
if (a == ']') return '[';
if (a == ')') return '(';
return 0;
}
bool isValid(char* s) {
int n = strlen(s);
if (n % 2 == 1) {
return false;
}
int stk[n + 1], top = 0;
for (int i = 0; i < n; i++) {
char ch = pairs(s[i]);
if (ch) {
if (top == 0 || stk[top - 1] != ch) {
return false;
}
top--;
} else {
stk[top++] = s[i];
}
}
return top == 0;
}
2)最长有效括号查找
给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = "(()" 输出:2 解释:最长有效括号子串是 "()" 示例 2:
输入:s = ")()())" 输出:4 解释:最长有效括号子串是 "()()" 示例 3:
输入:s = "" 输出:0
链接:https://leetcode-cn.com/problems/longest-valid-parentheses
提示:
最长有效括号子串的前一个字符有"(",")"或者空3种情况
最长有效括号子串的最后一个字符一定是")"
本题只需返回最长有效括号子串的长度即可
int longestValidParentheses(char* s) {
int sSize = strlen(s);
int *stack = (int*)malloc(sizeof(int)*(sSize+1)), top = -1, max = 0, i = 0;
top ++;
if(sSize > 0) stack[top] = -1; //增加哨兵
for(i = 0; i< sSize; i++)
{
if(s[i] == '(')
{
top ++;
stack[top] = i;
}
else if(top > 0)
{
top--; //弹栈
if(i - stack[top] > max) max = i - stack[top];
}
else stack[top] = i; //更新有效序列头元素
}
return max;
}
3)排队看人头问题
题目描述:有n个人站队,所有的人全部向右看,个子高的可以看到个子低的发型,给出每个人的身高,问所有人能看到其他人发型的总和数是多少。 输入:4 3 7 1 6 7 8 10 1 10 11 2 3 4 5 5 6 7
输出:2 解释:个子为4的可以看到个子为3的发型,个子为7可以看到个子为1的身高,所以1+1=2
提示:
使用单调栈进行求解
int find_result(int *nums, int numsSize)
{
int *stack = (int*)malloc(sizeof(int)*numsSize), top = -1, ret = 0, i;
for(i = 0; i<numsSize; i++)
{
while(top > -1)
{
if (nums[stack[top]] < nums[i]) top--;
else break;
}
if(top == -1) ret += i;
else
{
ret += i - stack[top];
top ++;
stack[top] = i;
}
}
return ret;
}
4)柱状图中的最大矩形
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
输入:heights = [0,2,1,5,6,2,3] 输出:10 解释:最大的矩形为图中红色区域,面积为 10
示例 2:
输入: heights = [2,4] 输出: 4
链接:https://leetcode-cn.com/problems/largest-rectangle-in-histogram/
提示:
可以从第一个柱子开始遍历依次计算以当前柱体为右边界时的最大矩形面积。
矩形覆盖范围内包含的所有柱子的高度都不小于矩形的高度
int max_find(int *nums, int numsSize)
{
int i, ret = 0, *stack = (int*)malloc(sizeof(int)*numsSize), top = -1, pre = 0;
top ++;
stack[top] = 0; //设置哨兵
for (i = 0; i<numsSize; i++)
{
pre = 0
while(top>0 && nums[stack[top]] > nums[i])
{
if (pre < nums[i] * (i - stack[top-1])) pre = nums[i] * (i - stack[top-1]);
top --;
}
int pro = top;
while(pro > 0)
{
if(pre < nums[stack[pro]]*(i - stack[pro-1])) pre = nums[stack[pro]]*(i - stack[pro-1]);
pro --;
}
if(pre > ret) ret = pre;
top ++;
stack[top] = i;
}
return ret;
}
5)中缀表达式求值
表达式求值是程序设计语言中的一个最基本问题。它的实现是栈应用的又一个典型例子。这里介绍一种简单直观、广为使用的算法,通常称为“算符优先法”。
要把一个表达式翻译成正确求值的一个机器指令序列,或者直接对表达式求值,首先要能够正确解释表达式。例如要对下述表达式求值:
4+(6-10+2*2)*2
首先,要了解算术四则运算的规则。即:
(1)先乘除,后加减; (2)从左算到右 (3)先括号内,后括号外 由此,这个算术表达式的计算顺序应为:
4+(6-10+2*2)*2 = 4+(-4+2*2)*2 = 4+(-4+4)*2 = 4+0*2 = 4+0 = 4
题目描述:
输入表示一个只包含四则运算符的中缀表达式的字符串 要求返回表示该中缀表达式结果的整形数据
输入:"4+(6-10+22)2"
输出:4
提示:
1)建立数据和运算符两个辅助栈帮助计算
2)遍历字符串遇到数据直接压栈 遇到运算符之后与运算符辅助栈中栈顶运算符进行优先级对比,优先级大于栈顶运算符
3.多路径匹配(树,图的遍历 回溯思想)
1)迷宫问题
求迷宫从入口到出口的所有路径是一个经典的程序设计问题。由于计算机解迷宫时,通常用的是“穷举求解”的方法,即从入口出发,顺某一方向向前探索,若能走通,则继续往前走;否则沿原路退回,换一个方向再继续探索,直至所有可能的通路都探索到为止。
为了保证在任何位置都能沿原路退回,显然需要用一个后进先出的结构来保存从入口到当前位置的路径。因此在迷宫求解时应用“栈”也就是自然而然的事情了。
在计算机中,我们可以用一个二维数组来表示一个迷宫,如下图所示:
我们使用字符1来表示迷宫中的墙体,即灰色的方块;使用字符0来表示可以通过的道路,即白色的方块。
求解迷宫的算法思想可以描述为:
初始化,将起点加入堆栈;
while(堆栈不为空){
取出栈顶位置为当前位置;
如果 当前位置是终点,
则 使用堆栈记录的路径标记从起点至终点的路径;
否则{
按照从下、右、上、左的顺序将当前位置下一个可以探索的位置入栈;
如果 当前位置的四周均不通
则 当前位置出栈;
}
}
注意
要避免重复走入同一位置