算法学习 (门徒计划)1-3 递归与栈 学习笔记
前言
3月18日,开课吧门徒计划算法课第三讲学习笔记。
本课讲递归和栈。
课题为:
1-3 递归与栈(Stack):解决表达式求值
(由于自身精力问题,本次只记录重点和次重点,尽量给算法题目配图)
来自3月30日
(学习完毕,记录过于详细了,并且没有多余精力为算法配图,下次需要改进)
递归与栈
栈(Stack)
栈的基本概念
栈是一种基础的线性数据存储结构。
栈可以想象成一个网球桶,最先放进去的球最后取出来。
栈的概念配图:
栈的基本操作
对于入栈的概念:
栈顶指针加1,存入目标元素
对于出栈的概念:
栈顶指针减1,提取目标元素(提取,等效于剪贴,先复制,再删除原始值)
(概念仅做参考实际使用时可以变通)
进栈,出栈,判空,元素遍历,长度获取演示代码
public static void test() throws Exception {
//以栈内数据结构是字符串类型为例子
//下方定义的这个栈无长度限制
Stack <String> stack = new Stack <String>();
//入栈
String in = stack.push("123"); //返回的是入栈的内容
boolean runRes = stack.add("45");//返回的是true或false
//在这种情况下不需要关注返回值
System.out.println(in);
System.out.println(runRes);
System.out.println(".....");
//遍历栈内元素,遍历的方式为从栈底向栈顶遍历
for(String str: stack) {
System.out.println(str);
}
System.out.println(".....");
//出栈
String out = stack.pop(); //输出并移出栈顶元素
String out2 = stack.peek();//仅输出栈顶元素
//第一次出栈删除元素,第二次出栈不删除元素仅读取
System.out.println(out);
System.out.println(out2);
//判断是否为空
boolean isEmpty= stack.isEmpty();
System.out.println(isEmpty);
//获取栈长度(压入2个元素,移出了一个)
Integer len = stack.size();
System.out.println(len);
}
打印如下:
123
true
…
123
45
…
45
123
false
1
栈的概念和应用
栈及相关概念常用于处理需要进行层次处理的场景。(队列常用于需要顺次处理的场景)
递归是栈的一种使用方式,因为每一层深入的递归都是新的层次,而对于上层而言都是确实是视作一个整体
举一个例子,有效括号的判断
(课上讲的例题是下方例题的简化版本,不完全相同)
例题:括号匹配 leetcode—20
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
解题思路
先不用栈,先考虑这个问题怎么从逻辑角度处理,这个问题显然是一个层次结构问题,在假设所有括号都正确闭合的情况下,那么任意一对闭合的括号内部区域也是可以正确闭合的,因此当发现某一个左括号后,就意味着期望在右侧合理的位置上发现一个右括号。
由于括号的种类并不相同因此不能只用一个数字来做加减,这个表示左括号数字的字符除了要表示括号的数量还需要表示括号的种类,还需要表示括号的出现顺序,因此可以指定一个字符类型数组,通过移动指针的方式来为括号进行匹配,如果遇到左括号就存入数组,并且指针右移,如果遇到右括号就取得数组最右端的内容如果相同就,指针左移一位,并清除,此时如果不相同就反馈为失败。最终如果所有数组指针没有归位,或者数组在归位时,遇到了取的要求,也判断失败。
(那么这种数组像什么?)
(像栈)
因此用栈来实现,遇到左括号入栈,右括号出栈,出栈前比较是否和存入的匹配。其余同理。最后判断某一次不匹配或者栈为空无法出栈或者结束时栈不为空,都为失败,其余为成功。
(记录用栈的解题思路)
(课上的例题括号类型相同因此可以考虑不用栈,改为一个整形变量存储栈的长度,这是因为括号类型相同不需要存储括号种类所以只需要进行出入栈动作,此时只和长度有关因此可以用一个变量代替栈这个对象)
(课上先讲了简单的题目用于活跃大脑,但是我没有必要记录因此直接解题)
示例代码
(以下我对于例题20的解法,但是性能不佳,先记录答案)
class Solution {
public boolean isValid(String s) {
//去除空格
s=s.replaceAll(" +","");
System.out.println(s); //test
char [] sc = s.toCharArray();
Stack <Character> stack = new Stack <Character>();
for(int i=0;i<sc.length;i++){
if( isleft_1(sc[i])||
isleft_2(sc[i])||
isleft_3(sc[i])){
stack.add(sc[i]);
}else{
if(!stack.isEmpty()){
Character c = stack.pop();
if(!chickBracket(c,sc[i]))
return false;
}else
return false;
}
}
if(!stack.isEmpty())
return false;
return true;
}
private boolean isleft_1(char c){
if(c=='(')
return true;
else
return false;
}
private boolean isleft_2(char c){
if(c=='[')
return true;
else
return false;
}
private boolean isleft_3(char c){
if(c=='{')
return true;
else
return false;
}
private boolean chickBracket(char c,char nsc){
switch(nsc){
case ')':
if(isleft_1(c))
return true;
else
return false;
case ']':
if(isleft_2(c))
return true;
else
return false;
case '}':
if(isleft_3(c))
return true;
else
return false;
default:
return false;
}
}
}
(下方是一个更为优秀的解法,和我的空间复杂度近似但是时间远胜于我)
这个方案通过使用map键值对的方式快速的进行了左右括号的匹配,性能远胜于我的用逻辑语句分析判断的性能,值得学习。
class Solution {
private static final Map<Character,Character> map = new HashMap<Character,Character>(){
{
put('{','}'); put('[',']'); put('(',')'); put('?','?');
}};
public boolean isValid(String s) {
if(s.length() > 0 && !map.containsKey(s.charAt(0))) return false;
LinkedList<Character> stack = new LinkedList<Character>() {
{
add('?'); }};
for(Character c : s.toCharArray()){
if(map.containsKey(c)) stack.addLast(c);
else if(map.get(stack.removeLast()) != c) return false;
}
return stack.size() == 1;
}
}
解题后的思维衍生
课上讲完题目后拓展了一下题目的思维,括号序列,本身也是一个层级结构,在试图执行左括号(理解为某一个函数的调用)时需要找到右括号,并内部的括号需要成对(理解为某一个函数内部有很多子函数),因此括号序列就和项目中的各种模块的代码很像,括号序列对于左右匹配用栈来实现,项目中不同函数的顺次排布或者是嵌套内在也是用栈来实现的。
(如何用栈实现的就暂时不记录了,后续如果没忘记再记录一下)
这道题目的意义:不在于实际的知识点在于看待问题的思维方式。
(关于这种思维方式,我认为我现在是掌握了,下一次看的时候我还能掌握吗)
最后对于这道题目做出了一个总结:
栈,可以处理具有完全包含关系的问题
(对此我的理解是:栈,可以用于处理有层次结构的问题,不知道和课上讲的内容是否一致)
(层次结构,就是包含了顺次执行的动作位,和每个动作作为整体依然可以有自身内在的动作位)
对此我自己举一个例子:
举一个例子:数学算式求值。
在数学运算式中不同的运算符号有不同的优先级,并且还有括号可以实现约定更高的优先级,每一次都需要先进行高优先级的计算,再将结果去参与下一轮运算。
例题:基本计算器 leetcode—224
(这是一道困难的题目,我自行解一遍,限时2h)
题目描述:给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
示例:
示例 1:
输入:s = “1 + 1”
输出:2
示例 2:
输入:s = " 2-1 + 2 "
输出:3
示例 3:
输入:s = “(1+(4+5+2)-3