数据结构(Java版)第十期:栈和队列(一)
专栏:数据结构(Java版)
个人主页:手握风云
目录
一、栈
1.1. 栈的概念
1.2. 栈的使用
1.3. 栈的模拟实现
二、栈的经典面试题
2.1. 逆波兰表达式
2.2. 有效的括号
2.3. 最小栈
一、栈
1.1. 栈的概念
栈是⼀种特殊的线性表,其只允许在固定的⼀端进⾏插⼊和删除元素操作。进⾏数据插⼊和删除操作 的⼀端称为栈顶,另⼀端称为栈底。栈中的数据元素遵守后进先出的原则。
入栈:栈的插⼊操作叫做进栈/压栈/⼊栈,⼊数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据在栈顶。
1.2. 栈的使用
import java.util.Stack;public class Main { public static void main(String[] args) { Stack stack = new Stack(); }}
我们点进去看一下Stack的源码:
public class Stack extends Vector { public Stack() { } public E push(E item) { addElement(item); return item; }
下面是一些Stack的方法,我们来实现一下。
import java.util.Stack;public class Main { public static void main(String[] args) { Stack stack = new Stack(); System.out.println(stack.empty());//检查栈是否为空 stack.push(5);//入栈 stack.push(4); stack.push(3); stack.push(2); stack.push(1); System.out.println(stack.size());//获取栈的元素个数 System.out.println(stack); System.out.println(stack.empty()); System.out.println(stack.pop());//栈顶元素出栈 System.out.println(stack.peek());//获取栈顶元素 }}
1.3. 栈的模拟实现
Stack继承了Vector,Vector和ArrayList类似,都是动态的顺序表,不同的是 Vector是线程安全的,我们可以直接用数组来实现栈。
import java.util.Arrays;public class MyStack { private int[] elem; private int usedSize; private static final int DEFAULT = 10; public MyStack(){ elem = new int[DEFAULT]; } public void push(int val){//模拟入栈操作 if(isFull()){//如果满了就扩容 grow(); } elem[usedSize] = val; usedSize++; } private void grow(){ elem = Arrays.copyOf(elem,elem.length); } public boolean isFull(){//判断数组是否满了 return usedSize == elem.length; } public int pop() { if(isEmpty()){ throw new StackIsEmpty(\"栈为空\"); } usedSize--;//相当于把数据置为null return elem[usedSize-1]; } public boolean isEmpty(){ return usedSize == 0; } public int peek() { if(isEmpty()){ throw new StackIsEmpty(\"栈为空\"); } return elem[usedSize--]; } public int size(){ return usedSize; }}
public class StackIsEmpty extends RuntimeException{ public StackIsEmpty() { super(); } public StackIsEmpty(String message) { super(message); }}
public class Main { public static void main(String[] args) { MyStack stack = new MyStack(); stack.push(1); stack.push(2); stack.push(3); stack.push(4); stack.push(5); System.out.println(stack.size()); int num1 = stack.pop(); System.out.println(num1); int num2 = stack.peek(); System.out.println(num2); }}
二、栈的经典面试题
2.1. 逆波兰表达式
逆波兰表达式,又称为后缀表达式,特点是运算符位于操作数之后。例如((2+1)*3)、(4+(13/5))是中缀表达式,转化为逆波兰表达式则为2 1 + 3 *、4 13 5 / +。
本题方法参数给出的是字符串数组,我们要先将字符转化为数字。先用引用去遍历数组,如果是数字,就放入栈中;如果引用指向的是运算符,则将栈顶的两个元素出栈进行运算。如此循环往复,直至遍历完整个字符串数组。
import java.util.Stack;public class Solution { public int evalRPN(String[] tokens){ Stack stack = new Stack(); int len = tokens.length; for (int i = 0; i < len; i++) {//遍历字符串数组 String str = tokens[i]; if(!isOperator(str)){ Integer num = Integer.valueOf(str);//将字符转化为数字 stack.push(num); }else{//如果是运算符,则栈顶两个元素出栈 Integer num1 = stack.pop(); Integer num2 = stack.pop(); switch (str){ case \"+\": stack.push(num2+num1); break; case\"-\": stack.push(num2-num1); break; case\"*\": stack.push(num2*num1); break; case\"/\": stack.push(num2/num1); break; } } } return stack.pop(); } private boolean isOperator(String val){//先判断字符串是数字还是运算符 if(val.equals(\"+\") || val.equals(\"-\") || val.equals(\"*\") || val.equals(\"/\")){ return true; } return false; }}
2.2. 有效的括号
题目中要求字符串只有大、中、小括号,返回true的条件为:当我们遍历完字符串之后并且栈为空。如果说,当我们左右括号不匹配时,比如\"([))\",就返回false;当我们遍历完字符串之后,如果栈不为空,也返回false,比如\"(()\";当我们还没有遍历完字符串,栈已经为空,也会返回false,比如\"({}))\"。
import java.util.Stack;public class Solution { public boolean isValid(String s){ Stack stack = new Stack(); int len = s.length(); for (int i = 0; i < len; i++) { char ch1 = s.charAt(i); if(ch1 == \'{\' || ch1 == \'[\' || ch1 == \'(\'){ stack.push(ch1);//将左括号入栈 }else{ if(stack.isEmpty()){ return false; } char ch2 = stack.peek();//获取入栈的左括号,与右括号进行匹配 if((ch2==\'{\'&&ch1==\'}\') || (ch2==\'[\'&&ch1==\']\') || (ch2==\'(\'&&ch1==\')\')){ stack.pop(); }else { return false; } } } if(!stack.isEmpty()){ return false; } return true; }}
2.3. 最小栈
如果我们抛开这道题,获取栈中的最小元素,我们就可以去遍历这个栈来找出我们的最小元素。但这道题限制我们需要在让时间复杂度为常数,所以说,一个栈是不能解决问题的,还需要在引入一个栈stack。
对于push方法,普通栈当中,所有数据都要放入,最小栈要对我们的普通栈第一次push进行维护。如果最小栈不为空,那么需要比较刚存放的数据与最小栈栈顶的数据进行比较,以对里面的最小值进行更新。这样顺便解决了getMin的实现,直接从最小栈栈顶获取。
对于pop方法,如果弹出的数据不是最小栈的栈顶数据,则只需要弹出普通栈的栈顶数据就行,否则则要弹出最小栈的栈顶数据。top相当于peek方法,只获取普通栈的栈顶元素。
这4个方法基本实现完成,但还有一个问题。如上图所示,如果我们再放入一个-1进入普通栈,那么最小栈需不需要再放入-1呢?答案是需要。因为按照我们的pop方法,把-1弹出,minstack也要弹出。
完整代码:
import java.util.Stack;public class MinStack { Stack stack = new Stack(); Stack minStack = new Stack(); public MinStack() { stack = new Stack(); minStack = new Stack(); } public void push(int val) { stack.push(val); if(minStack.empty()){ minStack.push(val); }else{ int peekMinVal = minStack.peek(); if(val <= peekMinVal){ minStack.push(val); } } } public void pop() { int val = stack.pop(); if(val == minStack.peek()){ minStack.pop(); } } public int top() { return stack.peek(); } public int getMin() { return minStack.peek(); }}