Author : Authur_Whywait. 忍不住化身一块疯狂吸收知识的海绵。( ̄︶ ̄)↗
想看博主的其他所有leetcode卡片学习笔记链接?传送门点这儿
本文中,我们将介绍另一种处理顺序后入先出
(LIFO),以及相应的数据结构栈
。
阅读本文之后,你将get以下技能:
- 理解 LIFO 和 栈的定义;
- 能够用动态数组实现栈;
- 熟悉内置栈结构;
- 能够使用栈解决问题。
后入先出的数据结构 - 栈
后入先出的数据结构
在 LIFO 数据结构中,将首先处理添加到队列中的最新元素。
与队列不同,栈是一个 LIFO 数据结构。
- 插入操作在栈中被称作入栈
push
。与队列类似,总是在堆栈的末尾添加一个新元素。 - 删除操作,退栈
pop
,将始终删除队列中相对于它的最后一个元素。
栈的实现比队列容易。使用动态数组足以实现堆栈结构。下面提供了一个简单栈的实现(C++版本)
#include <iostream>
class MyStack {
private:
vector<int> data; // store elements
public:
/** Insert an element into the stack. */
void push(int x) {
data.push_back(x);
}
/** Checks whether the queue is empty or not. */
bool isEmpty() {
return data.empty();
}
/** Get the top item from the queue. */
int top() {
return data.back();
}
/** Delete an element from the queue. Return true if the operation is successful. */
bool pop() {
if (isEmpty()) {
return false;
}
data.pop_back();
return true;
}
};
int main() {
MyStack s;
s.push(1);
s.push(2);
s.push(3);
for (int i = 0; i < 4; ++i) {
if (!s.isEmpty()) {
cout << s.top() << endl;
}
cout << (s.pop() ? "true" : "false") << endl;
}
}
栈 - 用法
大多数流行的语言都提供了内置的栈库,因此你不必重新发明轮子。除了初始化
,我们还需要知道如何使用两个最重要的操作:入栈
和退栈
。除此之外,你应该能够从栈中获得顶部元素
。
下面是C++语言版本关于栈的实现的代码实例:
#include <iostream>
int main() {
// 1. Initialize a stack.
stack<int> s;
// 2. Push new element.
s.push(5);
s.push(13);
s.push(8);
s.push(6);
// 3. Check if stack is empty.
if (s.empty()) {
cout << "Stack is empty!" << endl;
return 0;
}
// 4. Pop an element.
s.pop();
// 5. Get the top element.
cout << "The top element is: " << s.top() << endl;
// 6. Get the size of the stack.
cout << "The size is: " << s.size() << endl;
}
至于C,之前在队列的相关博文中就已经了解到,C并没有内置的队列库,自然,内置的栈库也是不存在的。(╯‵□′)╯︵┻━┻
相关程序练习
1. 最小栈
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
- push(x) – 将元素 x 推入栈中。
- pop() – 删除栈顶的元素。
- top() – 获取栈顶元素。
- getMin() – 检索栈中的最小元素。
Tips
和当初设计队列的时候一样,释放栈的时候要先释放对象中的数据结构(此情况下为数组),然后再释放对象;
void minStackFree(MinStack* obj) {
free(obj->val);
free(obj);
}
说明
因为栈属于LIFO的数据结构,所有的操作基本上都是只对top指针的操作,所以我在定义栈的时候,并没有将base指针写进去。
代码实现以及执行结果
typedef struct {
int* val;
int top;
} MinStack;
/** initialize your data structure here. */
MinStack* minStackCreate() {
MinStack* obj = (MinStack *)malloc(sizeof(MinStack));
if(!obj) return NULL;
obj->val = (int *)malloc(sizeof(int) * 10000);
obj->top = 0;
return obj;
}
void minStackPush(MinStack* obj, int x) {
obj->val[obj->top++] = x;
}
void minStackPop(MinStack* obj) {
obj->top--;
}
int minStackTop(MinStack* obj) {
return obj->val[obj->top-1];
}
int minStackGetMin(MinStack* obj) {
int min=minStackTop(obj);
for(int i=0; i<obj->top; i++)
min = min < obj->val[i] ? min : obj->val[i];
return min;
}
void minStackFree(MinStack* obj) {
free(obj->val);
free(obj);
}
/**
* Your MinStack struct will be instantiated and called as such:
* MinStack* obj = minStackCreate();
* minStackPush(obj, x);
* minStackPop(obj);
* int param_3 = minStackTop(obj);
* int param_4 = minStackGetMin(obj);
* minStackFree(obj);
*/
2. 有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
输入: “()[]{}”
输出: true
输入: “([)]”
输出: false
分析
主要分为以下几种情况:
- 如果是左括号系列直接入栈
- 如果是右括号系列且与栈顶元素匹配则栈顶元素出栈
- 如果是右括号系列且与栈顶元素不匹配,则返回false
- 如果整个字符串遍历结束,栈内仍用元素,则返回false;否则,返回true。
Tips
是否曾经有个想法在你脑海里浮现:一共三种类型六个括号,去判断他们是否匹配,得多麻烦啊,判断条件不得写一堆。
有一种方法是另写一个函数,专门判断两个新的括号和栈顶元素的括号是否匹配。
直接调用函数确实能减少代码量,但是我的小脑袋瓜灵光一下,有了一个自以为妙到绝处的想法。
将符号数字化。
什么是数字化呢?就是用数字来取代符号。我们按照我们的需要(判断是否匹配),将六个不同的括号按一定顺序放到一个字符串里面char symbol[7]="{[()]}";
。
为什么方括号里面的是7而不是6呢?因为一个字符串还有一个’\0’作为结束符。
如此这般,将括号转换为其索引(从0开始),那么判断两个括号是否匹配就变成它们两者的索引之和是否为5。至于左括号系列的就是index<3的部分,剩下的部分就都属于右括号系列了。
有其他的好方法一定要留言告诉我哦~ Ψ( ̄∀ ̄)Ψ
代码实现以及执行结果
typedef struct{
int array[10000];
int top;
} Stack;
bool isValid(char * s){
if(!s) return true;
char symbol[7]="{[()]}";
Stack *S = malloc(sizeof(Stack));
S->top = 0;
for(int i=0; i<strlen(s); i++){
int pos=0;
for( ; pos<6; pos++) if(symbol[pos]==s[i]) break;
if(pos<3) S->array[S->top++] = pos;
else {
if(S->top>0 && S->array[S->top-1]+pos==5) S->top--;
else return false;
}
}
if(S->top) return false;
else return true;
}
3. 每日温度
根据每日 气温
列表,请重新生成一个列表,对应位置的输出是需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0
来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73]
,你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]
。
提示:气温
列表长度的范围是 [1, 30000]
。每个气温的值的均为华氏度,都是在 [30, 100]
范围内的整数。
分析
懵逼树上懵逼果。懵逼树下你和我。
这题的解题方法有二,一为暴力法,一为使用栈。因为本文主题需要,本题使用栈来解题。
(暴力法就算了,一个难度中等的题目使用暴力法有点说不过去 ━┳━ ━┳━)
根据题目信息,我们需要找到比当前 T[i] 温度更高的位置,那么必须要记录哪些信息?
我们试着找到 T[0] 过后温度升高的位置。如果知道 T[10]=50,则 T[20]=50 是无效信息,因为 T[i] 在 T[20] 以前已经到达了 50。如果 t[20]=100 将是有用的信息,因为如果 t[0]=80,那么 T[20] 将有可能是它的下一个温度升高的位置,而 T[10] 则不可能是。
因此,我们需要记住一个索引的列表,索引代表的温度严格递增。我们可以利用栈来实现这样的效果。
是不是看不懂?莫得关系。下面的算法部分有举例,对照着例子,再看分析部分,就能理解啦。
算法
我们用栈记录索引,满足 T[stack[-1]] < T[stack[-2]] < …,其中 stack[-1] 是栈的顶部,stack[-2] 是从顶部开始的第二个元素,依此类推;我们将在处理每个 T[i] 时保持 stack[-1] > stack[-2] > …。
我们通过当前温度和栈顶索引所代表的温度比较来找到温度升高的位置。
举个例子:我们反向遍历处理 t=[73,74,75,71,69,72,76,73] ,通过看栈元素的变化来理解是如何工作的。为了清楚 stack 只包含索引 i,但是将把 T[i] 的值写在旁边的括号中,例如 0 (73)。
当 i = 7,stack = [7 (73)]。ans[i] = 0。
当 i = 6,stack = [6 (76)]。ans[i] = 0。
当 i = 5,stack = [5 (72), 6 (76)]。ans[i] = 1。
当 i = 4,stack = [4 (69), 5 (72), 6 (76)]。ans[i] = 1。
当 i = 3,stack = [3 (71), 5 (72), 6 (76)]。ans[i] = 2。
当 i = 2,stack = [2 (75), 6 (76)]。ans[i] = 4。
当 i = 1,stack = [1 (74), 2 (75), 6 (76)]。ans[i] = 1。
当 i = 0,stack = [0 (73), 1 (74), 2 (75), 6 (76)]。ans[i] = 1。
代码实现以及执行结果
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
typedef struct{
int* array;
int top;
} Stack;
int* dailyTemperatures(int* T, int TSize, int* returnSize){
* returnSize = TSize;
if(! TSize) return NULL;
int* waitingdays = malloc(sizeof(int) * TSize);
Stack * S = (Stack *)malloc(sizeof(Stack));
S->array = (int *)malloc(sizeof(int) * TSize);
S->top = 0;
for(int i=TSize-1; i>-1; i--){
if(S->top>0 && T[i]>=T[S->array[S->top-1]]){
S->array[S->top++] = i;
while(S->top>1 && T[S->array[S->top-1]]>=T[S->array[S->top-2]]){
S->array[S->top-2] = S->array[S->top-1];
S->top--;
}
}
else S->array[S->top++] = i;
if(S->top>1) waitingdays[i] = S->array[S->top-2] - S->array[S->top-1];
else waitingdays[i] = 0;
}
return waitingdays;
}
4. 逆波兰表达式求值
根据逆波兰表示法,求表达式的值。
有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
- 整数除法只保留整数部分。
- 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
输入: [“2”, “1”, “+”, “3”, “*”]
输出: 9
解释: ((2 + 1) * 3) = 9
分析
此题一看,马上用栈。但是,最棘手的问题不是栈的使用,而是,字符串转数字。。。(╯‵□′)╯︵┻━┻
每个字符串的字符长度不确定,可能还带有负号。用上以前讲的c-‘0’
,确实有那么点方便起来,但是真的也就只有那一点点了。
于是乎,就在这个时候,白马王子踩着筋斗云来拯救我们啦!蹬蹬蹬瞪,atoi函数华丽登场!
关于atoi函数的作用和用法,请进入传送门小白笔记本【函数篇】(updating)
其后就只剩代码实现了。
代码实现及执行结果
int evalRPN(char ** tokens, int tokensSize){
int stack[tokensSize];
int top=-1;
int a, b;
for (int i=0; i<tokensSize; i++){
if (strcmp(tokens[i], "+") == 0){
a = stack[top--];
b = stack[top--];
stack[++top] = b + a;
}
else if (strcmp(tokens[i], "-") == 0){
a = stack[top--];
b = stack[top--];
stack[++top] = b - a;
}
else if (strcmp(tokens[i], "*") == 0){
a = stack[top--];
b = stack[top--];
stack[++top] = b * a;
}
else if (strcmp(tokens[i], "/") == 0){
a = stack[top--];
b = stack[top--];
stack[++top] = b / a;
}
else stack[++top] = atoi(tokens[i]);
}
return stack[top];
}
都看到这里了,确定不点个赞再走?(╹ڡ╹ )