📖前言
最近刷了几道栈的题目,觉得很深刻,想写个题解,想通过题解对自己的思路再一次总结,并且加深映像,准备通过发布到掘金来记录,由于笔者之前没有怎么写过算法题的总结,这是第一篇正式的对算法题的总结,所以有很多地方写的可能没有那么好,望多多海涵!
本文主要是以JavaScript的代码为主,其它代码就不过多介绍了,通过分享三道题对栈类的算法题进行总结
JavaScript栈的实现
实际上,JavaScript不像其它语言一样,直接提供了栈的实现,JS的栈的实现实际上是通过数组来进行的!
看了mdn发现,数组有push方法和pop方法可以实现入栈和出栈,还可以通过下标[length-1]来获取
栈顶元素
const animals = ["pigs", "goats", "sheep"];
const count = animals.push("cows"); //入栈 返回新的栈长度
console.log(count) //4
const value = animals.pop() //出栈 返回栈顶的值
console.log(value) //cows
console.log(animals[animals.length - 1]); //获取栈顶元素 此时是sheep
有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
首先,题目给了三个有效的条件,那咱们就把无效的字符串的情况罗列出来。
//1. {]
//2. {}}}
//3. ))
//4. ()(((
//5. (
//6. }
这六种就是全部的不符合的情况,我们需要指定算法将这六种情况全部排除:
对s进行遍历:
1.若是[、{、(的话,则入栈
2.若是]、}、)的话,则进行分类讨论:
对于]
如果如果栈顶此时是[,那就pop()栈顶的[然后继续。
-
栈顶此时不是
[,则直接返回false(排除了1、2、3、6这种情况)。因为我们发现如果栈顶不是[,栈顶可能为空,那就是2、3、6这种情况,此时入栈]后,这个s一定是非法的,因为栈的特点:先进后出,后面没有人能把]给消掉,因此无论如何]入栈了之后就一定会存在,导致闭不合,从而无效。 -
栈顶也可能为除了
{或者(,这个时候与入栈的]不匹配,此时入栈],同样的,由于后面没有人能把]给消掉,导致]会一直存在,导致闭不合。
对于}和)也是同样的,只有栈顶此时是对应的可以消除的符号{和(,咱们才能消除,然后继续,否则和上面的情况一样,直接返回false。
咱们在初始为栈顶设置一个与题目不相干的0,作为初始栈顶
最后如果发现咱们的栈顶是0的,说明所有的入栈括号都能全部消除了,符合规则,返回true。
如果咱们的栈顶不为空,说明有括号没对应的括号匹配从而消除,此时返回false(情况4,5)
具体代码实现如下:
const stack = []; //初始化栈
stack.push(0); //初始栈顶为0
//遍历字符串
for(e of s)
{
//如果是左半边符号,则直接入栈
if(e === '[' || e === '{' || e === '(')
{
stack.push(e);
}
//如果是右半边符号,进行分类讨论判断
else if(e === ']')
{
//仅有对应着左半边的符号,才能消除并且继续
if(stack[stack.length - 1] === '[')
stack.pop();
else
{return false;}
}
else if(e === '}')
{
if(stack[stack.length - 1] === '{')
stack.pop();
else
{return false;}
}
else if(e === ')')
{
if(stack[stack.length - 1] === '(')
stack.pop();
else
{return false;}
}
}
//当全部消除完了,符合题意
if(stack[stack.length - 1] === 0)
return true;
//否则,会有未消除的左符号
else
return false;
这道题主要是让我们熟悉一下栈,是一道很经典的入门题了。
最小栈
这道题让我们实现一个栈,通过观察发现其它的步骤都能够很轻松的实现,问题是getMin获取堆栈中的最小元素,并且还要以常数时间检索到最小元素。
一开始我在做这道题的时候,看到“常数”,“检索”,立马联想到了哈希表,可是哈希表是无序的,后面想了下结合链表,但是发现插入的时候找到最小值可能时间复杂度不止O1,所以这两者方式看来都是不可行的,我看了题解发现给出了 一个我第一次见的做法---辅助栈,不得不说第一次做这个题如果能够想到辅助栈,我觉得是真NB!
我们发现,在入栈元素a时,栈里有其它的元素b,c,d,之后不论经过了哪些操作,在a被弹出来之前,b,c,d一定不会被弹出来,假设此时有一个最小值,那么在a被弹出来之前,a作为栈顶时的最小值永远不会发生变化。
所以我们可以维护一个最小值的栈min_stack,我们可以巧妙地发现,当每个元素入栈时,可以通过比较当前元素和上一个状态的最小值,来O1地更新min_stack,将每一个元素入栈时的状态对应的最小值 入栈到 这个min_stack,从而我们就可以随时查找每个状态的最小值了,只需要取这个min_stack的栈顶就行了,取出来也是O1的时间复杂度。
接下来是代码部分:
var MinStack = function() {
//初始化栈
this.x_stack = [];
//初始化最小值栈,栈顶设置无限大的元素,使得第一次就能够更新最小值
this.min_stack = [Infinity];
};
MinStack.prototype.push = function(x) {
//入栈
this.x_stack.push(x);
//维护这个状态的最小值
this.min_stack.push(Math.min(this.min_stack[this.min_stack.length - 1], x));
};
MinStack.prototype.pop = function() {
//出栈
this.x_stack.pop();
//当前状态的最小值可能会发生变化,更新
this.min_stack.pop();
};
MinStack.prototype.top = function() {
//正常获取栈顶
return this.x_stack[this.x_stack.length - 1];
};
MinStack.prototype.getMin = function() {
//获取我们维护的最小值
return this.min_stack[this.min_stack.length - 1];
};
很巧妙地利用好了入栈时可以更新最小值,并且最小值放入栈中与原栈同步,实现了O1的时间复杂度来进行最小值的查找
字符串解码
我认为这道题是最难的一道,看到题目时会联想到栈,但怎么用好栈在这道题上才是最关键的。
题目的这种格式,我们一般都会从里往外看,脑海里先构造[]里面的内容,然后再往外一层一层构造出完整的结果,但是咱们遍历的话一般是从外往里构造,从左往右,越里面的越先构造,反而外面的后构造,那肯定是栈的先进后出了,后进先出了。
咱们用脑子从左到右模拟一遍过程,发现每次遇到]时才是我们结果的构造,而每次遇到]时,我们就会想要获得上一个[旁边的数字,和上一个[和这一个]里面的结果。
如果没有嵌套结构的话,那咱们就直接用一个number变量记录数字,用一个res变量记录结果就好了,反正每次都只有一个,但问题就是咱们题目里有时候会出现嵌套结构。
于是就不能使用单个变量了,因为我们需要获取当前的[和]外面的信息了,我们通过观察发现这种嵌套结构可以很好地用栈来表示,于是我们就准备分别用两个栈来维护每一层[]的number和每一层[]的res,分别是number栈和res栈。
具体在嵌套时,我们模拟一遍发现,此时的当前的[和]需要获取最近的nunber,也就是number栈的栈顶,还需要最近的res结果,使得当前的res = res * number + last_res,这样子直到字符串结束,刚好此时的res正是我们从内向外迭代的最终结果!
接下来是具体代码:
/**
* @param {string} s
* @return {string}
*/
var decodeString = function(s) {
const res_stack = [];
const number_stack = [];
let res = "";
//1.遍历
//3.遇到数字 直接放入数字的栈
//4.遇到[ 放入res栈 并且清空 ; 数字放入,并且清0
//5. 遇到] res * 取出的数字 + 前一个res 赋值给res
let number = 0;
for (char of s)
{
if(!isNaN(char))
{
//遇到数字,进行处理
number = number*10 + Number(char);
}
else if(char == '[')
{
//遇到 [
//res放入栈,并且清空; number放入栈,并且清0
res_stack.push(res);
res = "";
number_stack.push(number);
number = 0;
}
else if(char == ']')
{
//遇到 ]
//更新当前res即可,并且pop栈
let number = number_stack[number_stack.length - 1];
let last_res = res_stack[res_stack.length - 1];
res = last_res + res.repeat(number);
number_stack.pop();
res_stack.pop()
}
else{
//普通字符append
res += char;
}
}
return res;
};
🎯总结
总的来说,我们在做算法时,观察到题目中涉及到先进后出时,涉及到括号匹配等,我们就需要考虑栈的使用,需要仔细思考,可以先用脑子大概捋一遍流程,然后得出算法的实现!
🌇结尾
感谢你看到最后,最后再说两点~
①如果你持有不同的看法,欢迎你在文章下方进行留言、评论。
②如果对你有帮助,或者你认可的话,欢迎给个小点赞,支持一下~
我是3Katrina,一个热爱编程的大三学生
(文章内容仅供学习参考,如有侵权,非常抱歉,请立即联系作者删除。)
作者:3Katrina
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。