栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除的一端称为栈的顶,另一端称为的栈的底。栈中元素遵循后入先出的原则。
栈的常用方法:
Stack() 构造一个空栈
push(e) 将e压入栈,并返回e
pop() 将栈顶元素出栈并返回
peek() 获取栈顶元素
size() 获取栈中有效元素个数
empty() 检测栈是否为空
下面用数组模拟构造一个栈
int[] element = new int[2];//创建一个数组,用来模拟栈
int usesize = 0;//用来记录栈中的有效数据的个数
模拟实现栈的功能
/**
* 入栈
*/
public void inStack(int data){
if(justFull()){//判断栈是否满
//如果满了就扩容
this.element = Arrays.copyOf(this.element,2*this.element.length);
}
element[usesize++] = data;
}
/**
*出栈
*/
public int pop(){
if(justempty()){
throw new RuntimeException("栈为空");
}
int data = element[usesize-1];//如果是引用类型,要将它置为空
usesize--;
return data;
}
/**
*获取栈顶元素
*/
public int gettopStatic(){
if(justempty()){
throw new RuntimeException("栈为空");
}
return element[usesize-1];
}
/**
*获取栈中有效元素个数
*/
public int getStaticnum(){
return usesize;
}
//判断栈是否空
public boolean justempty(){
return usesize==0;
}
//判断栈是否满
public boolean justFull(){//如果有效数据的个数等于栈的长度就表示满了
return usesize == element.length;
}
主函数里面实现一下
public static void main(String[] args) {
Static aStatic = new Static();
aStatic.inStack(21);
aStatic.inStack(22);
aStatic.inStack(24);
aStatic.inStack(28);
aStatic.inStack(39);
System.out.println(aStatic.pop());
System.out.println(aStatic.gettopStatic());
System.out.println(aStatic.pop());
System.out.println(aStatic.getStaticnum());
if(aStatic.justempty()){
System.out.println("栈为空");
}else{
System.out.println("栈不为空");
}
}
结果
有关栈的试题
1.括号表达式 力扣
思路:
1.规定遇到左括号就入栈
2.遇到右括号,则用当前的右括号和栈顶元素比较。如果匹配则出栈,继续遍历字符串,遇到不匹配的则返回false。
3.字符串遍历完成后,如果栈中还有数据,则说明左括号多,返回false
4.当栈为空了,字符串还没有遍历完,就说明右括号多,返回false
5.当上面遍历完了,没有返回false,则说明括号有效,返回true
class Solution {
public boolean isValid(String s) {
// 1.规定遇到左括号就入栈
// 2.遇到右括号,则用当前的右括号和栈顶元素比较。如果匹配则出栈,继续遍历字符串,遇到不匹配的则返回false。
// 3.字符串遍历完成后,如果栈中还有数据,则说明左括号多,返回false
// 4.当栈为空了,字符串还没有遍历完,就说明右括号多,返回false
// 5.当上面遍历完了,没有返回false,则说明括号有效,返回true
Stack<Character> stack = new Stack<>();
for(int i = 0;i<s.length();i++){
char ch = s.charAt(i);
if(ch=='('||ch=='{'||ch=='['){//遇到左括号就入栈
stack.push(ch);
}else{//遇到右括号,则用当前的右括号和栈顶元素比较
if(stack.empty()){//当栈为空了,字符串还没有遍历完,就说明右括号多,返回false
return false;
}
if(ch==')'&&stack.peek()=='(' || ch=='}'&&stack.peek()=='{' || ch==']'&&stack.peek()=='['){//如果栈顶元素和匹配,则将栈顶元素弹出
stack.pop();
}else{//遇到不匹配的则返回false
return false;
}
}
}
if(!stack.empty()){//字符串遍历完成后,如果栈中还有数据,则说明左括号多,返回false
return false;
}else{
return true;
}
}
}
结果
2.逆波兰表达式求值力扣
思路:
1.创建一个类型为整形的栈
2.用一个变量x记录下标为i的字符串,判断字符串是不是数字,是就将该字符串转化为数字并压入栈中。不是则说明该字符串是运算符。
3.第2步如果是数字,则继续遍历数组。如果是运算符,则弹出栈顶两个数字并记录。此时判断运算符是那种类型,然后对应着做相应的运算,并将运算结果压入栈中,然后再执行循环。
4.最后退出循环,弹出栈中的数字并返回。
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for(int i = 0;i<tokens.length;i++){
String x = tokens[i];
if(juststring(x)){//成立则说明进栈的是数字
stack.push(Integer.parseInt(x));
}else{//则说明要进栈的是运算符,此时弹出栈顶两个数字分别放在运算符右边和左边进行运算
int num2 = stack.pop();
int num1 = stack.pop();
switch(x){
case "+":stack.push(num1+num2);
break;
case "-":stack.push(num1-num2);
break;
case "*":stack.push(num1*num2);
break;
case "/":stack.push(num1/num2);
break;
}
}
}
return stack.pop();
}
public boolean juststring(String s){
if("+".equals(s)||"-".equals(s)||"*".equals(s)||"/".equals(s)){return false;}
return true;
}
}
结果
3.出栈次序匹配 栈的压入、弹出序列_牛客题霸_牛客网
思路:
1.首先判断pushA和popA是否为空,如果其中一个为空,则返回null
2.通过循环将pushA中的数字要入栈中,在压栈中。如果压入的数字和popA中的第一个数相等,则弹出该数字,同时popA的下表向后移动到下一个数字,再次和栈顶的数字比较。相等则继续,当栈为空或者不相等则不进入比较,pushA继续压栈。 循环完毕返回栈是否为空即可,为空则匹配成功,反之不成功。
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
if(pushA==null||popA==null){//如果其中有一个为空,就返回false
return false;
}
int j = 0;
Stack<Integer> stack = new Stack<>();
for(int i = 0;i<pushA.length;i++){
stack.push(pushA[i]);
while(!stack.empty()&&stack.peek()==popA[j]){//当压入的数字和序列数组中相应的数字相等,则将栈里面的数字弹出
stack.pop();
j++;
}
}
return stack.empty();
}
}
结果
二。队列
队列:只允许在一段进行插入数据操作,在另一端进行删除数据操作,进入插入操作的一段称为队尾,进行删除操作的一段称为队头。队列遵循先进先出的原则。
队列的常用方法
offer(e) 入队列
poll() 出队列
peek() 获取队头元素
size() 获取队列中有效元素个数
isEmpty() 判断队列是否为空
用链表模拟实现队列(要求时间复杂度O(1))
方法:创建两个指针head、last。head指向队列头,last指向队列尾
public class myQueue {
class Node{//创建节点类
public int val;
public Node next;
public Node(int val) {
this.val = val;
}
}
Node head = null;//队列头
Node last = null;//队列尾
int usedsize = 0;//用来记录队列中的节点数量
}
模拟实现Queue中的方法
public class myQueue {
class Node{
public int val;
public Node next;
public Node(int val) {
this.val = val;
}
}
Node head = null;//队列头
Node last = null;//队列尾
int usedsize = 0;//用来记录队列中的节点数量
/**
* 入队列:尾巴入
*/
public void offer(int data){
Node node = new Node(data);
if(head==null){//说明队列里面没有节点
head = node;
last = node;
}else{
last.next = node;
last = last.next;
}
usedsize++;
}
/**
* 出队列:从头出
*/
public int poll(){
if(justempty()){//先判断链表是否为空,为空就不能出队列,抛异常
throw new RuntimeException("队列空");
}
int val = head.val;
head = head.next;
if(head==null){//当只有一个节点的时候,弹出去以后,last也要跟着置为空
last = null;
}
usedsize--;
return val;
}
/**
*获取队列头元素
*/
public int peek(){
if(justempty()){//先判断链表是否为空,为空就不能出队列,抛异常
throw new RuntimeException("队列空");
}
return head.val;
}
/**
*获取队列中有效元素个数
*/
public int size(){
return usedsize;
}
//判断队列是否为空
public boolean justempty(){
return usedsize==0;
}
}
主函数里面实现一下
public class Test {
public static void main(String[] args) {
myQueue queue = new myQueue();
queue.offer(1);
queue.offer(12);
queue.offer(31);
queue.offer(11);
System.out.println(queue.poll());
System.out.println(queue.justempty());
}
}
结果
用数组模拟环形队列力扣
思路:数组中空一个位置出来预留后面的操作
1.创建两个指针front、rear。front指向队列头,rear指向队列尾巴。当front==rear时,表示队列空;当rear = (rear+1)%elem.length()==front时表示队列满
class MyCircularQueue {
private int[] elem;
private int front;//表示对列头下表
private int rear;//表示队列尾下表
public MyCircularQueue(int k) {//完成对数组的初始化
elem = new int[k+1];//这里因为空留了一个数组位置,在初始化的时候多加1,就能表达预先完整的数组
}
/**
*入队列
* 1.判断是否满
* 2.插入数据:把当前需要存放的元素放在rear下表
*/
public boolean enQueue(int value) {
if(isFull()){
return false;
}else{
elem[rear] = value;
rear = (rear+1)%elem.length;
return true;
}
}
/**
*出队列
* 队列为空
*/
public boolean deQueue() {
if(isEmpty()){
return false;
}
front = (front+1)%elem.length;
return true;
}
/**
*获取队列头元素
*/
public int Front() {
if(isEmpty()){
return -1;
}
return elem[front];
}
/**
*获取队列尾元素
*/
public int Rear() {
if(isEmpty()){
return -1;
}
int index = (rear==0)?elem.length-1:rear-1;
return elem[index];
}
/**
*判断队列是否为空,当rear==front时,队列为空
*/
public boolean isEmpty() {
return rear==front;
}
/**
* 判断队列是否满
* 浪费一个位置来判断是否满
*/
public boolean isFull() {
return (rear+1)%elem.length==front;
}
结果
面试题
1.用队列实现栈力扣
思路:创建两个队列s1、s2
1.压栈,判断“栈”是否为空,如果空返回-1。不为空,s1、s2中必有一个或者两个都不为空。 s1不为空就将数据放入队列s1中,s2不为空就将数据放入队列s2中,如果两个都不为空则放入s1中。
2.出栈,判断“栈”是否为空,为空返回-1。不为空,则判断是s1还是s2不为空,将不为空的那个链表中要弹出的那个数据之前的所有数据都放在另一个链表中,最后弹出该数字。如果另一个链表不为空,则同样的方法操作。
3.返回栈顶元素,将第2步的弹出数据,不弹出而是一样放在另一个链表中。这里需要创建一个零时变量temp来接收队列中的数据。在返回的时候只用返回最后的temp即可
4.判断是否为空,只需要返回s1.isEmpty()&&s2.isEmpty()即可
class MyStack {
Queue<Integer> s1;
Queue<Integer> s2;
public MyStack() {//创建两个队列
s1 = new LinkedList<>();
s2 = new LinkedList<>();
}
public void push(int x) {
/**
*入栈操作:默认将数字压在不为空的队列中,如果两个队列都为空,则默认将数字压入s1这个队列中
*因此在压入数字之前要判断队列是否为空
*/
if(!s1.isEmpty()){
s1.offer(x);
}
else if(!s2.isEmpty()){
s2.offer(x);
}else{
s1.offer(x);
}
}
/**
*出栈操作:出栈数字在不为空的队列中,将该数字上面的所有数字放在另一个为空的队列中。以此往复直到栈里面最后一个数字被弹出
*/
public int pop() {
//判断当前"栈"是否为空,如果为空就返回-1
if(empty()){return -1;}
//判断那个队列不为空
if(!s1.isEmpty()){
int len = s1.size();
for(int i = 0;i<len-1;i++){
int temp = s1.poll();
s2.offer(temp);
}
return s1.poll();
}else{
int len = s2.size();
for(int i = 0;i<len-1;i++){
int temp = s2.poll();
s1.offer(temp);
}
return s2.poll();
}
}
public int top() {
//判断当前"栈"是否为空,如果为空就返回-1
if(empty()){return -1;}
int temp = 0;
//判断那个队列不为空
if(!s1.isEmpty()){
int len = s1.size();
for(int i = 0;i<len;i++){
/**
*将该数字和它上面的数字一起让在另一个为空的队列中,因为经过了中间temp,所以后面只需要返回temp的值即可
*/
temp = s1.poll();
s2.offer(temp);
}
return temp;
}else{
int len = s2.size();
for(int i = 0;i<len;i++){
temp = s2.poll();
s1.offer(temp);
}
return temp;
}
}
public boolean empty() {
return s1.isEmpty()&&s2.isEmpty();
}
}
结果
2.用栈实现队列 力扣
思路:
1.创建两个栈s1、s2
2.入队的时候,放到第一个栈当中
3.出队的时候,出s2当中的元素。如果第二个栈当中没有元素,那么就把第一个栈里面的元素都倒在 s2当中。
class MyQueue {
Stack<Integer> s1;
Stack<Integer> s2;
public MyQueue() {//创建两个栈
s1 = new Stack<>();
s2 = new Stack<>();
}
public void push(int x) {//默认将数据都压入s1中
s1.push(x);
}
public int pop() {//默认移除s2中的数据,如果s2中没有数据,就将s1中的数据全部倒在s2中
if(empty()){return -1;}
if(s2.empty()){
while(!s1.empty()){
s2.push(s1.pop());
}
}
return s2.pop();
}
public int peek() {
if(empty()){
return -1;
}
if(s2.empty()){
while(!s1.empty()){
s2.push(s1.pop());
}
}
return s2.peek();
}
public boolean empty() {
return s1.empty()&&s2.empty();
}
}
结果
3.实现一个最小栈力扣
思路:
创建两个栈s1、minStack。s1用来存放数据,minStack用来记录s1中最小值
1.当第一个数字被压进栈,这个数字两个栈都要压。当第二个数字被压进栈,s1直接进,进minStack时,要和minStack中的数字进行比较,小于该数字才能进。
2.出栈时,s1直接出,如果minStack中有该数字(在栈顶)也要跟着出
总结:minStack里面栈顶存放的是s1栈中的最小值
class MinStack {
Stack<Integer> s1;
Stack<Integer> minstack;
public MinStack() {
s1 = new Stack<>();
minstack = new Stack<>();
}
public void push(int val) {
s1.push(val);
if(minstack.empty()){
minstack.push(val);
}else{
int x = minstack.peek();
if(val<=x){
minstack.push(val);
}
}
}
public void pop() {
int x = s1.pop();
if(x==minstack.peek()){
minstack.pop();
}
}
public int top() {
return s1.peek();
}
public int getMin() {
return minstack.peek();
}
}
结果