栈是常见的数据结构之一,我将在这篇博客中简要介绍 栈、栈的使用、单调栈、栈和DFS、栈和递归。
栈
栈(stack) 又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。
栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
栈的使用
- stl库实现
在C++中的stl库中存在已经封装好的栈已经栈相关的函数,我们可以直接使用。
头文件:#include <stack>
定义在std空间中: using namespace std;
创建栈: stack<数据类型> stk(自定义栈名);
栈的基本操作函数:
stk.push(X);将元素X放入栈中
stk.pop();删除栈尾元素
stk.size();返回栈中元素的个数
stk.empty();如果栈为空返回true,不空返回false
stk.top();访问栈顶元素
- 手写栈
手写栈比使用stl库的效率更高,更节约时间,所以一般在算法竞赛中会选择用数组模拟实现栈。
int stk[n];创建栈
int cnt = 0;指向栈顶的指针
stk[++cnt] = x //x入栈,++cnt是因为通常会空出下标为0的作为判断栈是否为空,其次++cnt使cnt所指是栈顶元素
cnt--; //弹出栈顶元素
cnt >0 //cnt > 0则栈不为空,cnt == 0则栈为空
//cnt的值表示栈里有多少个元素
stk[cnt];// 栈顶元素
好了,你已经会使用栈了()。
单调栈
单调栈是栈的一个常见的使用方法。
顾名思义,单调栈是保证栈中元素时刻为单调序列的栈。
单调栈的实现:
新的元素入栈时从栈顶开始判断,如果不满足单调条件就弹出栈顶元素,循环直到满足单调条件或者栈为空,再将该元素入栈。
while(!stk.empty() && stk.top() < / > target)
stk.pop();
stk.push(target)
涉及数组的问题时,通常我们是用栈存储数组下标,而不是数组元素。
单调栈的应用范围我知道的不全面,只能例举几道单调栈道题目。
503. 下一个更大元素 II
由题意可知,这道题使用的是一个单调减的栈。
弹出栈的元素的下一个更大元素就是正要入栈的元素。
我们只需要对[原数组 + 前n - 1个元素]的数组用单调栈遍历一边即可,最后留在栈中的便是没有下一个更大元素的元素对此,我们只需要初始化答案容器(c++里是vector,c里为数组)为-1即可。
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n,-1);//初始化为-1
int stk[2*n - 1];//栈存储数组下标,便于填充答案数组
int cnt = 0;
for(int i = 0; i < 2*n - 1; i++)
{
int tmp = i % n;
while(cnt > 0 && nums[stk[cnt]] < nums[tmp])
{
ans[stk[cnt]] = nums[tmp];
cnt--;
}
stk[++cnt] = tmp;
}
return ans;
}
};
402. 移掉 K 位数字
这道题是贪心+单调栈可以解决。
对数字143XXXX,删掉第一个数字时,显然14XXX > 13XXX,所以选择删掉4依次类推,原理是位数越高,影响越大。也就是贪心,我总是要去掉高位的数字。
char * removeKdigits(char * num, int k){
if(strlen(num) == k)
return "0";
int len = strlen(num);
char stk[100010]={};
int head = 0 , i = 0;
while(i < len)
{
while(head > 0 && stk[head] > num[i] && k)
{
head--;
k--;
}//每弹出一个,去除一位数
stk[++head] = num[i];
i++;
}//单调栈走完后,栈中留下去除一定位数的数据
head -= k;//由于可能还没有去除k位数字就已经结束了,由于栈中此时是单调增的,我们只需要从后往前去除剩下几位数
char* ans = (char*)malloc(sizeof(char)*(len+1));
int q = 1 , j = 0;
while(q <= head && stk[q]=='0')
q++;//除去可能存在的前导零
for(; q <= head; q++)
{
ans[j++] = stk[q];
}//复制处理后的数据
if(j == 0)//如果没有可以复制的数据,则答案为0,并添加结束的0
{
ans[0] = '0';
ans[1] = 0;
}
else
ans[j] = 0;//字符串末位添加0结束
return ans;
}
栈与DFS
由于栈先进后出的性质,通常会和DFS(深度优先搜索)练习在一起。
深度优先搜索可以用递归、迭代等方式实现,比较常见的是在二叉树的遍历中(不过二叉树遍历还有一个比较难的算法,问就是不会)。DFS,通俗讲就是一条路走到黑,撞到南墙才回头走另一条岔道。
这和栈先进后出的性质很符合,比如说
二叉树的前序遍历:对深度第二的一个节点 node -> node、node->left -> node -> node、node->right
我们访问到node ,下一步让左子节点进栈,经过条件判断后左子节点没有子节点了,左子节点出栈,右子节点进栈、出栈,然后node出栈,栈顶元素来到了node的父节点。
例题:
733. 图像渲染
由于我写的是BFS,就直接用了官方的代码
class Solution {
public:
string removeKdigits(string num, int k) {
vector<char> stk;
for (auto& digit: num) {
while (stk.size() > 0 && stk.back() > digit && k) {
stk.pop_back();
k -= 1;
}
stk.push_back(digit);
}
for (; k > 0; --k) {
stk.pop_back();
}
string ans = "";
bool isLeadingZero = true;
for (auto& digit: stk) {
if (isLeadingZero && digit == '0') {
continue;
}
isLeadingZero = false;
ans += digit;
}
return ans == "" ? "0" : ans;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/remove-k-digits/solutions/484940/yi-diao-kwei-shu-zi-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
栈与递归
由于c语言中调用函数时分配给函数的是内存中栈的空间。
因此,函数的递归调用,可以看作是一个栈。
第一层函数在栈底,随着不断递归调用,新的函数不断向栈顶添加,如果这时递归结束条件没有设置好,就会导致栈的内存溢出,程序异常终止。
用栈的方式理解函数的递归调用,或许会更好理解一点。