栈是操作受限的特殊的线性表(顺序存储和链式存储),即有顺序栈和链栈。
栈的特点是:先进后出,只允许在固定的一端进行插入和删除。
栈的方法有:
方法 | 功能 |
---|---|
Stack() | 构造一个空的栈 |
push(E e) | 入栈 |
pop() | 出栈 |
peek() | 获取栈顶元素 |
size() | 得到栈的大小 |
empty() | 判断栈是否为空 |
栈:是一种数据结构
虚拟机栈:是Java运行程序的一块内存
栈帧:是在虚拟机栈上给函数开辟的内存空间
题力扣20. 有效的括号:给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。 有效字符串需满足:
左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括号。
分析:
把给定的输入字符串依次压栈,如果遇到右括号就弹出栈进行匹配。
不匹配一共有3种情况,①左括号多了,当输入字符串读完的时候,栈还没空;②右括号多了,当栈为空的时候,还读入了右括号;③不匹配,右括号和出栈的左括号不匹配
代码:
class Solution {
public boolean isValid(String s) {
Deque<Character> stack = new ArrayDeque<>();//实例化栈
for (int i = 0; i < s.length(); i++) {
char ch = s.charAt(i);
if (ch == '(' || ch == '{' || ch == '[') {
//入栈
stack.push(ch);
}else { //ch为右括号
if (stack.isEmpty()) {
return false;
}
char ch2 = stack.peek(); //左括号
if (ch == ')' && ch2 == '(' || ch == '}' && ch2 == '{' || ch == ']' && ch2 == '[') {
stack.pop();
}else {
return false; //不匹配
}
}
}
if (stack.isEmpty()) {
return true;
}
//左括号多了
return false;
}
}
题:力扣150. 逆波兰表达式求值 给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:
有效的算符为 ‘+’、‘-’、‘*’ 和 ‘/’ 。
每个操作数(运算对象)都可以是一个整数或者另一个表达式。
两个整数之间的除法总是 向零截断 。
表达式中不含除零运算。
输入是一个根据逆波兰表示法表示的算术表达式。
答案及所有中间计算结果可以用 32 位 整数表示。
分析:逆波兰式又叫后缀表达式,将中缀表达式转换成后缀表达式,利用栈来进行求值,例如将 9+(3-1)*3+10/2
转换成后缀表达式,根据运算符的优先级顺序结果为:931-3*+102/+
。
那么计算后缀表达式的值,利用栈,将数组压入栈中,如果遇到符号,那么就弹出2个数字进行符号运算,得到的结果又压栈。
代码:
class Solution {
public int evalRPN(String[] tokens) {
Stack<String> stack = new Stack<>(); //实例化栈
for (int i = 0; i < tokens.length; i++) {
if (!judeYun(tokens[i])) {//不是运算符
stack.push(tokens[i]);
}else {//是运算符
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int ret = count(tokens[i],num1,num2);
String s = String.valueOf(ret);
stack.push(s);
}
}
return Integer.parseInt(stack.pop());
}
private boolean judeYun(String s) {
if (s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")) {
return true;
}
return false;
}
private int count(String s,int num1, int num2) {
switch (s){
case "+": return num1 + num2;
case "-": return num1 - num2;
case "*": return num1 * num2;
case "/": return num1 / num2;
default: return 0;
}
}
}
题:力扣剑指 Offer 31. 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
分析:这个题目在选择题也经常出现,通过入栈的序列来判断给出的弹出栈的序列是否合法。压入的序列在数组pushed中,弹出序列在数组poped中,如果即将压入的数字,等于弹出数字中指向的下标对应的值就压入并且弹出。
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Deque<Integer> stack = new LinkedList<>(); //实例化队列
int j = 0;
for (int i = 0; i < pushed.length; i++) {
stack.push(pushed[i]);
while (!stack.isEmpty() && j < popped.length && popped[j] == stack.peek()) {
j++;
stack.pop(); //相等的那么出栈
}
}
if (!stack.isEmpty() || j < popped.length) {
return false;
}
return true;
}
}
题:力扣155. 最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。
void push(int val) 将元素val推入堆栈。
void pop() 删除堆栈顶部的元素。
int top() 获取堆栈顶部的元素。
int getMin() 获取堆栈中的最小元素。
分析:要快速得到栈的最小元素,那么就实例化2个栈,一个是普通栈,另外一个是实时存放最小元素的栈。即借助一个辅助栈minStack用于存储获取stack普通栈的最小值。
class MinStack {
public Stack<Integer> stack;
public Stack<Integer> minStack;
public MinStack() {
stack = new Stack<>(); //普通栈
minStack = new Stack<>(); //最小栈
}
public void push(int val) {
if (stack.isEmpty()) {
stack.push(val);
minStack.push(val);
return;
}
stack.push(val);
if (val <= minStack.peek()) {
minStack.push(val);
}
}
public void pop() {
int x = stack.pop();
if (x == minStack.peek()) {
minStack.pop();
}
}
public int top() {
if (stack.isEmpty()) {
return -1;
}
return stack.peek();
}
public int getMin() {
if (minStack.isEmpty()) {
return -1;
}
return minStack.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(val);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
队列是一种操作首先的线性表,只允许在一端进行插入,另一端进行删除,具有先进先出的特性,插入的那端叫做队头,删除的这端叫做队尾。
队列中的操作方法:
方法 | 功能 |
---|---|
offer(E e) | 入队列 |
poll() | 出队列 |
peek() | 获取队头元素 |
size() | 得到队列的大小 |
empty() | 判断队列是否为空 |
队列也有链式存储和线性存储,其中线性存储通常实现的是循环队列,队列也有一种叫双端队列Deque。
循环队列通常是队列的顺序存储,当采用顺序存储存储队列的时候,就会造成底层数组出现“虚满”,从而提出循环队列的思想。那么循环队列如何区分队空和队满呢?【3种方法】
①添加计数器
②牺牲一个内存空间
③使用标记
通常采用得是第二种方法,那么此时:
队空:rear == front
堆满:rear + 1 == front
力扣622. 设计循环队列
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。
代码:
class MyCircularQueue {
public int[] elem;
public int front; //队头
public int rear; //队尾
public MyCircularQueue(int k) {
elem = new int[k+1]; //牺牲一个内存单元
}
public boolean enQueue(int value) {
//入队要判断队列是否满
if (isFull()) {
return false;
}
//此时队列不满
elem[rear] = value;
rear = (rear + 1 + elem.length) % elem.length;
return true;
}
public boolean deQueue() {
if (isEmpty()) {
return false;
}
front = (front + 1 + elem.length) % elem.length;
return true;
}
public int Front() {
if (isEmpty()) {
return -1;
}
return elem[front];
}
public int Rear() {
if (isEmpty()) {
return -1;
}
int index = rear - 1 >= 0 ? rear - 1 : elem.length - 1;
return elem[index];
}
public boolean isEmpty() {
if (rear == front) {
return true;
}
return false;
}
public boolean isFull() {
if ((rear + 1 + elem.length) % elem.length == front) {
return true;
}
return false;
}
}
/**
* Your MyCircularQueue object will be instantiated and called as such:
* MyCircularQueue obj = new MyCircularQueue(k);
* boolean param_1 = obj.enQueue(value);
* boolean param_2 = obj.deQueue();
* int param_3 = obj.Front();
* int param_4 = obj.Rear();
* boolean param_5 = obj.isEmpty();
* boolean param_6 = obj.isFull();
*/
在Java种,Queue是一个接口,底层是通过链表实现的。
//栈的实例化
Stack<Integer> stack = new Stack<>(); //不太常用
//双端队列实现栈
Deque<Integer> stack1 = new LinkedList<>();
Deque<Integer> stack2 = new ArrayDeque<>();
//队列的实例化
Queue<Integer> queue = new LinkedList<>();
// Queue queue1 = new ArrayList<>(); 报错
Deque是一个接口,栈和队列均可以使用该接口。
力扣225. 用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
分析:模拟入栈的时候,哪个队列不为空的时候就入哪个队列,当要出栈的时候,就把不为空的队列的元素移到为空的那个队列,并且把最后一个元素留下输出,相当于出栈。【相互倒】
代码:
import java.util.LinkedList;
import java.util.Queue;
//225. 用队列实现栈
class MyStack {
//定义2个队列
public Queue<Integer> queue1;
public Queue<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
if (queue1.isEmpty() && queue2.isEmpty()) {
queue1.offer(x);
}else if (queue1.isEmpty()) {
queue2.offer(x);
}else {
queue1.offer(x);
}
}
public int pop() {
if (queue1.isEmpty() && queue2.isEmpty()) {
return -1;
}
if (!queue1.isEmpty()) {
int size = queue1.size();
while (size - 1 != 0) {
queue2.offer(queue1.poll());
size--;
}
return queue1.poll();
}
if (!queue2.isEmpty()) {
int size = queue2.size();
while (size - 1 != 0) {
queue1.offer(queue2.poll());
size--;
}
return queue2.poll();
}
return -1;
}
public int top() {
if (queue1.isEmpty() && queue2.isEmpty()) {
return -1;
}
if (!queue1.isEmpty()) {
int size = queue1.size();
while (size - 1 != 0) {
queue2.offer(queue1.poll());
size--;
}
int val = queue1.poll();
queue2.offer(val);
return val;
}
if (!queue2.isEmpty()) {
int size = queue2.size();
while (size - 1 != 0) {
queue1.offer(queue2.poll());
size--;
}
int val = queue2.poll();
queue1.offer(val);
return val;
}
return -1;
}
public boolean empty() {
if (queue1.isEmpty() && queue2.isEmpty()) {
return true;
}
return false;
}
}
/**
* Your MyStack object will be instantiated and called as such:
* MyStack obj = new MyStack();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.top();
* boolean param_4 = obj.empty();
*/
力扣232. 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
分析:一个栈专门用来入元素,当要出元素的时候,把专门用来入元素的栈的元素全部移动到栈2中,以后每次出队列就在栈2中出,直至栈2为空,再把栈1的元素拿过来【专注自己的功能事】
代码:
import java.util.Deque;
import java.util.LinkedList;
class MyQueue {
public Deque<Integer> satck1;
public Deque<Integer> satck2;
public MyQueue() {
satck1 = new LinkedList<>();
satck2 = new LinkedList<>();
}
public void push(int x) {
//选取一个栈,从一而终
satck1.push(x);
}
public int pop() {
if (empty()) {
return -1;
}
if (!satck2.isEmpty()) {
return satck2.pop();
}
while (!satck1.isEmpty()) {
satck2.push(satck1.pop());
}
return satck2.pop();
}
public int peek() {
if (empty()) {
return -1;
}
if (!satck2.isEmpty()) {
return satck2.peek();
}
while (!satck1.isEmpty()) {
satck2.push(satck1.pop());
}
return satck2.peek();
}
public boolean empty() {
return satck1.isEmpty() && satck2.isEmpty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/