一、基本实现
1.栈
package com.wschase.stack;
/**栈:
* 首先对于栈的实现我们有两种方法:顺序表、链表;但是我们只需要掌握顺序表实现就可以了
* 对于栈:有一个栈底(bottom)、栈顶(top)——表示当前栈顶的元素
* Author:WSChase
* Created:2019/4/6
*/
/**
* 这个栈这个类——它就是我们的顺序表,所以它里面的属性和我们的顺序表是一样的,都是一个数组和数组元素的个数
* 注意:我们先不考虑扩容的情况
*/
class Stack{
int array[];
//表示当前栈里面元素的个数
int top;
}
public class MyStack {
/**
* 下面实现栈的一些基本方法
*/
//1.栈的初始化
public void stackInit(Stack s){
s.top=0;
}
//2.进栈:给定一个栈、给定一个元素——需要将这个元素放进栈里面
public void stackPush(Stack s,int v){
//由于我们的栈有先进后出的特性,所以我们只能对尾部进行操作,所以实际上就是做顺序表的尾插
s.array[s.top++]=v;
}
//3.出栈
public void stackPop(Stack s){
s.top--;
}
//4.查看栈顶元素
public int stackTop(Stack s){
return s.array[s.top-1];
}
//5.查看元素个数
public int stackSize(Stack s){
return s.top;
}
//6.判断栈是否为空
public boolean isStackEmpty(Stack s){
return s.top==0;
}
}
2.队列
package com.wschase.queue;
/**队列:
* 队列的特点就是先进先出,它的实现方法也可以采用改造的顺序表/单链表(头删+尾插)
* 我们采用单链表来实现队列
* Author:WSChase
* Created:2019/4/6
*/
/**
* 链表的结点类
*/
class Node{
int val;
Node next;
}
/**
* 队列的类
*/
class Queue{
Node head;
Node last;
}
public class MyQueue {
/**
* 下面是队列一些方法的实现
* 注意:我们对队列的操作就是对链表的头删和尾插
*/
//1.队列的初始化
public void queueInit(Queue q){
q.head=q.last=null;
}
//2.尾插--进栈
public void queuePush(Queue q,int v){
//首先将我们需要插入的值转化为一个结点
Node node=new Node();
node.val=v;
node.next=null;
if(q.head==null){
q.head=q.last=node;
}else {
q.last.next=node;
q.last=node;
}
}
//3.头删--出栈
public void queuePop(Queue q){
q.head=q.head.next;
if(q.head==null){
q.last=null;
}
}
//4.查看队首元素
public int queueFront(Queue q){
return q.head.val;
}
//5.返回队列元素个数
public int queueSize(Queue q){
int size=0;
for(Node cur=q.head;cur!=null;cur=cur.next){
size++;
}
return size;
}
//6.判断队列是否为空
public boolean isQueueEmpty(Queue q){
return q.head==null;
}
}
二、面试题
package com.wschse.stack;
/**栈:
* 下面是关于栈的面试题——栈的应用
* Author:WSChase
* Created:2019/4/7
*/
import java.util.Stack;
/**
* 在java里面是自带栈这个类的,所以下面的面试题如果使用到栈我们可以直接使用java里面的栈进行操作,
* 但是我们也可以自己重新定义一个自己的栈进行操作。下面我们直接使用java自定义的栈。
*/
public class Solution {
/**
* 1.给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。
* 有效字符串需满足:
* 左括号必须用相同类型的右括号闭合。
* 左括号必须以正确的顺序闭合。
* 注意:空字符串可被认为是有效字符串。
* 分析:
* 我们很容易发现这样的特性符合栈的特性,我们将左括号放进栈里面,如果遇到了右括号那么就将右括号于栈顶元素进行比较
* 如果匹配那么直接将栈顶元素出栈,如果不匹配则进行比较字符数组的下一个元素,如果栈里面为空了还有右括号那么则返回false。
*/
// public boolean isValid(String s) {
// //首先需要创建一个栈
// Stack<Character> stack=new Stack<>();
// //接下来对字符串进行分情况判断——
// //但是需要注意的是我们传进去的参数是String类型的,但是我们在判断的时候是单个字符进行判断的,所以需要将字符串转化为字符数组
// char[] ch=s.toCharArray();
// for(int i=0;i<ch.length;i++){
// char c=ch[i];
// switch (c){
// //如果遇到是左括号的情况我们就直接一起处理——入栈
// case '(':
// case '[':
// case '{':
// stack.push(c);
// break;
// //如果是右括号则进行判断
// case ')':
// case ']':
// case '}':
// //(1)当出现了右括号,但是栈里面已经没有左括号可以进行匹配了,那么就是无效的字符串
// if(stack.empty()){
// return false;
// }else {
// //将当前栈里面栈顶的元素取出来进行比较:这个时候又会出现两种情况匹配|不匹配
// char out=stack.peek();
// //取出来的括号匹配有三种情况:
// if( (out=='('&&c==')')
// ||(out=='['&&c==']')
// ||(out=='{'&&c=='}')
// ){
// //一旦满足就将左括号出栈
// stack.pop();
// }else {
// //如果不匹配
// return false;
// }
// }
// }
// }
// //等到这个字符所有的元素都入栈并且判断完成以后还需要进行一步校验——栈是否为空,
// // 如果校验完成以后栈里面还有元素,那么说明左括号比右括号多。
// if(!stack.empty()){
// return false;
// }else {
// return true;
// }
}
/**
* 2.使用队列实现栈的下列操作:
* push(x) -- 元素 x 入栈
* pop() -- 移除栈顶元素
* top() -- 获取栈顶元素
* empty() -- 返回栈是否为空
* 注意:
* 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
* 你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
* 你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。
*/
//class MyStack {
//
// /** Initialize your data structure here. */
// public MyStack() {
//
// }
// //在后面栈的实现我们需要使用队列,所以我们实例化一个队列
// Queue<Integer> queue=new LinkedList<>();
//
// /** Push element x onto stack. */
// public void push(int x) {
// //对于进栈的操作和队列是一样的
// queue.add(x);
// }
//
// /** Removes the element on top of the stack and returns that element. */
// public int pop() {
// int size= queue.size();
// //通过队列来实现栈的不同指出就体现在出栈的这个地方
// //我们首先要经历(k-1)次的出队列再入队列将最后一个元素挪到出队列的地方
// for(int i=0;i<size-1;i++){
// int v=queue.peek();
// queue.poll();
// queue.add(v);
// }
// //经过上面的for循环以后此时队首的元素就是队尾的元素了
// int val=queue.peek();
// queue.poll();
// //返回的是最后进入队列的元素
// return val;
// }
//
// /** Get the top element. */
// public int top() {
// //这个是查看栈顶元素,它和上面出栈基本是相同,唯一的区别就是不需要出栈了,只是查看就行了;
// //但是我们还需要考虑的是:当我们将队列中最后一个元素挪到队首进行查看以后,如果不将它放回原来的位置
// //那么下次出栈的元素就不是对应的元素了
// int size= queue.size();
// //通过队列来实现栈的不同指出就体现在出栈的这个地方
// //我们首先要经历(k-1)次的出队列再入队列将最后一个元素挪到出队列的地方
// for(int i=0;i<size-1;i++){
// int v=queue.peek();
// queue.poll();
// queue.add(v);
// }
// //经过上面的for循环以后此时队首的元素就是队尾的元素了
// int val=queue.peek();
// //经过下面的两个步骤就将队列变回原来的顺序了
// queue.poll();
// queue.add(val);
// //返回的是最后进入队列的元素
// return val;
// }
//
// /** Returns whether the stack is empty. */
// public boolean empty() {
//
// return queue.isEmpty();
// }
//}
/**
* 3.使用栈实现队列的下列操作:
* push(x) -- 将一个元素放入队列的尾部。
* pop() -- 从队列首部移除元素。
* peek() -- 返回队列首部的元素。
* empty() -- 返回队列是否为空。
* 示例:
* MyQueue queue = new MyQueue();
*
* queue.push(1);
* queue.push(2);
* queue.peek(); // 返回 1
* queue.pop(); // 返回 1
* queue.empty(); // 返回 false
*/
/**
* 分析:
* 队列的实现需要通过两个栈,一个栈用于放进栈的顺序(逆序)-in,一个放出栈的顺序(正序)-out
* 从这点我们可以看出,栈可以将数组里面的元素逆序。
* 但是我们需要注意的是,当出栈的时候如果我们的left为空才将元素从right挪动到left,否则直接出栈
*/
//class MyQueue {
//
// /** Initialize your data structure here. */
// public MyQueue() {
//
// }
// //需要两个栈
// Stack<Integer> out =new Stack<>();
// Stack<Integer> in =new Stack<>();
//
// /** Push element x to the back of queue. */
// public void push(int x) {
// //对于进队列就是栈的入栈,这个没有区别
// in.push(x);
//
// }
//
// /** Removes the element from in front of queue and returns that element. */
// public int pop() {
// //出队列:我们需要通过两个栈来实现队列的先进先出的原则
// if(out.empty()){
// while (!in.empty()){
// int v=in.peek();
// in.pop();
// out.push(v);
// }
// }
// //等到经历过这个条件判断以后out里面已经是正序的顺序了
// int v=out.peek();
// out.pop();
// return v;
// }
//
// /** Get the front element. */
// public int peek() {
// //出队列:我们需要通过两个栈来实现队列的先进先出的原则
// if(out.empty()){
// while (!in.empty()){
// int v=in.peek();
// in.pop();
// out.push(v);
// }
// }
// //等到经历过这个条件判断以后out里面已经是正序的顺序了
// int v=out.peek();
// return v;
// }
//
// /** Returns whether the queue is empty. */
// public boolean empty() {
// //两个栈同时为空的时候这个队列才为空
// return in.empty()&&out.empty();
//
// }
//}
/**
* 4.设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
* push(x) -- 将元素 x 推入栈中。
* pop() -- 删除栈顶的元素。
* top() -- 获取栈顶元素。
* getMin() -- 检索栈中的最小元素。
* 示例:
* MinStack minStack = new MinStack();
* minStack.push(-2);
* minStack.push(0);
* minStack.push(-3);
* minStack.getMin(); --> 返回 -3.
* minStack.pop();
* minStack.top(); --> 返回 0.
* minStack.getMin(); --> 返回 -2.
*/
//class MinStack {
//
// /** initialize your data structure here. */
// public MinStack() {
//
// }
// /**
// 对于这个最小栈的实现我们采用两个栈来实现,我们通过一次遍历来实现也就是时间复杂度为O(1)
// 但是我们的空间复杂度就不能是O(1)了,相当于是用时间来换空间。
// 对于这两个栈,一个栈里面存放我们入栈的元素,一个栈里面存放我们的最小元素
// */
// //s1存放元素
// Stack<Integer> s1=new Stack<>();
// //s2存放最小元素
// Stack<Integer> s2=new Stack<>();
//
//
// public void push(int x) {
// //对于s1的进栈没有变化就是正常进栈
// s1.push(x);
// //元素但是对于s2的进栈就需要考虑到比较每次进展元素的大小,始终保证是最小的
// if(s2.empty()){
// s2.push(x);
// }else{
// if(x<s2.peek()){
// s2.push(x);
// }else{
// s2.push(s2.peek());
// }
// }
// }
//
// public void pop() {
// //对于出栈就是两个栈都同时出栈就可以了
// s1.pop();
// s2.pop();
//
// }
//
// public int top() {
// //查看栈顶元素
// int v=s1.peek();
// return v;
// }
//
// public int getMin() {
// //获取最小的元素就是直接在s2中得到栈顶的元素就是最小的
// int v=s2.peek();
// return v;
// }
//}
/**
* 5.设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
* 循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
* 你的实现应该支持如下操作:
* MyCircularQueue(k): 构造器,设置队列长度为 k 。
* Front: 从队首获取元素。如果队列为空,返回 -1 。
* Rear: 获取队尾元素。如果队列为空,返回 -1 。
* enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
* deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
* isEmpty(): 检查循环队列是否为空。
* isFull(): 检查循环队列是否已满。
*/
/**
* 我们的循环队列是通过数组来实现的,只是这个数组中我们定义了几个特殊含义的变量:
* rear:当前可用队尾的下标=(rear+1+capacity)%capacity
* front:当前队首下标=(front+1+capacity)%capacity
* index:当前元素的数组下标,可以通过它获取当前元素
*/
class MyCircularQueue {
//我们实现自己的循环队列:通过数组实现
//保存数据的数组
private int[] array;
//这个数组(循环队列)的容量
private int capacity;
//循环队列中有效数据个数
private int size;
//当前队首下标
private int front;
//当前可用队尾的下标
private int rear;
/** Initialize your data structure here. Set the size of the queue to be k. */
public MyCircularQueue(int k) {
array=new int[k];
capacity=k;
size=0;
front=0;
rear=0;
}
/** Insert an element into the circular queue. Return true if the operation is successful. */
public boolean enQueue(int value) {
//入队列
if(size==capacity){
return false;
}
array[rear]=value;
rear=(rear+1)%capacity;
this.size++;
return true;
}
/** Delete an element from the circular queue. Return true if the operation is successful. */
public boolean deQueue() {
//出队列
if(size==0){
return false;
}
front=(front+1)%capacity;
this.size--;
return true;
}
/** Get the front item from the queue. */
public int Front() {
//获取队首
if(size==0){
return -1;
}
return array[front];
}
/** Get the last item from the queue. */
public int Rear() {
//返回队尾元素
if(size==0){
return -1;
}
//因为rear表示对是当前可用队尾的下标,所以是最后一个元素的下一个空间,所以需要减1
int index=((rear-1)+capacity)%capacity;
return array[index];
}
/** Checks whether the circular queue is empty or not. */
public boolean isEmpty() {
return this.size==0;
}
/** Checks whether the circular queue is full or not. */
public boolean isFull() {
//对于这句话:array[front]==array[rear])&&我们可以不要,因为我们判断队列是空还是满是通过计数器的方式来进行判断的
//但是加上它以后运行的时间要快些
return (array[front]==array[rear])&&(this.size==capacity);
}
}