栈和队列是两种非常重要的数据结构,都是逻辑结构,也就是说,栈(Stack),先进后出(后进先出),只要符合这种逻辑的数据结构都可以称为栈,队列(Queue),先进先出(后进后出),只要符合这种逻辑结构的数据结构,都可以称为队列。java中,有Stack这个类,但是很少用,因为它底层使用的是Vector(和ArrayList基本一样,唯一不同的是,里面的方法是synchronized修饰的,因此是线程安全的,但是效率低,所以很少用),大部分情况都是使用LinkedList作为栈和队列,具体用法可以看LinkedList用作栈和队列。
1 用数组实现栈
定义一个top,指向栈顶元素,size,记录栈中元素个数,当往栈中放一个元素,top++,size++,从栈中取出一个元素,- -top,并返回arr[top],size- -。
代码
//用数组结构实现大小固定的队列和栈
public class Stack{
public Integer[] arr;
public Integer size;
public Integer top;
public Stack(int initSize){
if(initSize < 0){
throw new IllegalArgumentException("The init size is less than 0");
}
arr = new Integer[initSize];
size = 0;
top = 0;
}
public void push(int i){
if(size > arr.length - 1){
throw new ArrayIndexOutOfBoundsException("this stack is full");
}
arr[top++] = i;
size++;
}
public Integer pop(){
if(size == 0){
throw new ArrayIndexOutOfBoundsException("this stack is empty");
}
size--;
return arr[--top];
}
public Integer peek(){
if(size == 0){
return null;
}
return arr[top-1];
}
}
2 用数组实现队列
定义一个first、一个last指针,first指向队首元素,last指向队尾元素。向队列中添加元素时,将元素添加到数组索引为last的位置,之后,判断last是否等于arr.length - 1,等于的话就让last等于0,不等于就加1。从队列中取出元素时,直接取出arr[first],之后,判断first是否等于arr.length-1,等于的话就让first等于0,不等于的话,就让first加1。frist、last互不干扰。
代码
//用数组实现队列
public class Queue{
Integer[] arr;
Integer first;
Integer last;
Integer size;
public Queue(int initSize){
if(initSize < 0){
throw new IllegalArgumentException("the init size is less than 0");
}
arr = new Integer[initSize];
first = 0;
last = 0;
size = 0;
}
public void offer(Integer i){
if(size >= arr.length){
throw new ArrayIndexOutOfBoundsException("this queue is full");
}
arr[last] = i;
size++;
//当数组满的时候,就让last指针为0
last = last == arr.length - 1 ? 0 : last + 1;
}
public Integer poll(){
if(size == 0){
throw new ArrayIndexOutOfBoundsException("this queue is empty");
}
int temp = arr[first];
size--;
//当first等于arr.length - 1 的时候,让first等于0
first = first == arr.length - 1 ? 0 : first + 1;
return temp;
}
public Integer peek(){
if(size == 0){
return null;
}
return arr[first];
}
}
3. 实现一个特殊的栈
实现一个特殊的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作。
【要求】
- pop、push、getMin操作的时间复杂度都是O(1)。
- 设计的栈类型可以使用现成的栈结构。
解题思路
定义两个栈,一个栈用于存放所有数据,另一个辅助栈用来存放最小值,开始的时候,将第一个元素同时压入两个栈,之后,再压入元素,第一个栈直接压入,然后判断这个元素是否比辅助栈中的栈顶元素小,小就压入辅助栈,不小的话,就将辅助栈中的栈顶元素压入。当弹出元素的时候,要同时弹出辅助栈中的栈顶元素。
public class Stack2{
//定义两个栈,第一个栈用来存放所有数据,第二个栈用来存放,当前数中的最小值
Deque<Integer> stackData;
Deque<Integer> stackMin;
public Stack2(){
stackData = new LinkedList<>();
stackMin = new LinkedList<>();
}
public void push(Integer i){
//当向栈中压入元素时,和最小值栈中的栈顶元素比,如果小于就压入,大于的话
// 就压入最小值栈中栈顶元素
if(stackMin.isEmpty()){
stackMin.push(i);
}else if(this.getMin() > i){
stackMin.push(i);
}else{
stackMin.push(stackMin.peek());
}
stackData.push(i);
}
public Integer pop(){
if(stackData.isEmpty()){
throw new ArrayIndexOutOfBoundsException("this stack is empty");
}
stackMin.pop();
return stackData.pop();
}
public Integer getMin(){
if(stackMin.isEmpty()){
throw new ArrayIndexOutOfBoundsException("this stack is Empty");
}
return stackMin.peek();
}
}
这道题还有一种解法是,只有当压入的元素小于或者等于辅助栈栈顶元素的时候,才压入辅助栈,否则不压入,从栈中取出元素的时候,只有当取出的元素和辅助栈栈顶元素相等时,辅助栈才会弹出栈顶元素。push()方法和pop()方法如下:
public void push(int newNum) {
if (this.stackMin.isEmpty()) {
this.stackMin.push(newNum);
} else if (newNum <= this.getmin()) {
this.stackMin.push(newNum);
}
this.stackData.push(newNum);
}
public int pop() {
if (this.stackData.isEmpty()) {
throw new RuntimeException("Your stack is empty.");
}
int value = this.stackData.pop();
if (value == this.getmin()) {
this.stackMin.pop();
}
return value;
}
第三种解法
手写链表,以空间换时间
class MinStack {
/*
手写链表实现,实现栈,空间换时间
*/
private class Node {
int val;
Node next;
int min;
public Node(int val, int min, Node next) {
this.val = val;
this.min = min;
this.next = next;
}
}
private Node head;
/** initialize your data structure here. */
public MinStack() {
}
public void push(int x) {
if(head == null) {
head = new Node(x, x, null);
}else {
//如果head不为空,就新建一个head,这个head的next指向上一个head(栈)
//这个head的最小值,指向新head和老head之间的最小值
head = new Node(x, Math.min(head.min, x), head);
}
}
public void pop() {
head = head.next;
}
public int top() {
return head.val;
}
public int min() {
return head.min;
}
}
4. 用栈实现队列结构
用栈实现队列结构,定义两个栈stack1、stack2,stack1负责存数据,stack2负责弹出数据,向队列中放数据时,直接将数据压入stack1,从队列中取出元素时,先判断stack2是否为空,不为空的话,就弹出栈顶元素,为空的话,就将stack1中的元素都压入stack2,如果stack1也为空的话,说明,队列中没有元素。
代码
public class TwoStackQueue{
Deque<Integer> stack1;
Deque<Integer> stack2;
public TwoStackQueue(){
stack1 = new LinkedList<>();
stack2 = new LinkedList<>();
}
public void offer(Integer i){
stack1.push(i);
}
public Integer poll(){
if(stack2.isEmpty()){
if(stack1 .isEmpty()){
throw new RuntimeException("this queue is empty");
}
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public Integer peek(){
if(stack2.isEmpty()){
if(stack1 .isEmpty()){
return null;
}
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
return stack2.peek();
}
}
5. 用队列实现栈结构
队列,先进先出,栈,先进后出。定义两个队列queue1、queue2,向栈中压入数据时,直接将数据存到queue1中,从栈中取数据时,先将queue1中的数据存到queue2,只留最后一个数据在queue1中,弹出。之后,将queue2赋给queue1、将queue1赋给queue2,以便下次存或取数据。peek()操作和pop()操作类似,也是将queue1中的元素除最后一个以外都存到queue2,然后记录这个值,再将最后一个元素存到queue2,交换queue1、queue2,最后返回刚记录的那个值。
代码
public class TwoQueueStack {
private Queue<Integer> queue;
private Queue<Integer> help;
public TwoQueueStack(){
queue = new LinkedList<>();
help = new LinkedList<>();
}
public void push(Integer i){
queue.offer(i);
}
public Integer pop(){
if(queue.isEmpty()){
throw new RuntimeException("the stack is empty");
}
while(queue.size() != 1){
help.offer(queue.poll());
}
int res = queue.poll();
swap();
return res;
}
public Integer peek(){
if(queue.isEmpty()){
return null;
}
while(queue.size() != 1){
help.offer(queue.poll());
}
int res = queue.poll();
help.offer(res);
swap();
return res;
}
public void swap(){
Queue<Integer> temp = help;
help = queue;
queue = temp;
}
}
6. 猫狗队列
题目
实现一种猫狗队列的结构,要求如下:
用户可以调用offer方法将cat类或dog类的实例放入队列中;
用户可以调用pollAll方法,将队列中所有的实例按照进队列的先后顺序依次弹出;
用户可以调用pollDog方法,将队列中dog类的实例按照进队列的先后顺序依次弹出;
用户可以调用pollCat方法,将队列中cat类的实例按照进队列的先后顺序依次弹出;
用户可以调用isEmpty方法,检查队列中是否还有dog或cat的实例;
用户可以调用isDogEmpty方法,检查队列中是否有dog类的实例;
用户可以调用isCatEmpty方法,检查队列中是否有cat类的实例。
解题思路
定义两个队列,catQueue、dogQueue,catQueue用于存放Cat、dogQueue用于存放Dog,这样pollDog、pollCat、isDogEmpty、isCatEmpty方法,都可以实现,再定义一个PetEnterQueue类,封装Dog、Cat,并且加一个count属性,来记录dog、cat的顺序,调用pollAll方法时,比较catQueue、dogQueue中的Pet的count属性,谁小就先弹出谁。
代码
public class Code_04_DogCatQueue {
public static class Pet{
private String type;
public Pet(String type){
this.type = type;
}
public String getType(){
return type;
}
}
public static class Dog extends Pet{
public Dog(){
super("dog");
}
}
public static class Cat extends Pet{
public Cat(){
super("cat");
}
}
public static class PetEnterQueue {
private Pet pet;
private Long count;
public PetEnterQueue(Pet pet, Long count){
this.pet = pet;
this.count = count;
}
public Pet getPet() {
return pet;
}
public Long getCount() {
return count;
}
}
public static class DogCatQue{
private Deque<PetEnterQueue> dogQue;
private Deque<PetEnterQueue> catQue;
private Long count;
public DogCatQue(){
dogQue = new LinkedList<>();
catQue = new LinkedList<>();
count = 0l;
}
public void offer(Pet pet){
if(pet.getType().equals("dog")){
dogQue.offer(new PetEnterQueue(pet, count++));
}else{
catQue.offer(new PetEnterQueue(pet, count++));
}
}
public Pet pollAll(){
if(!catQue.isEmpty() && !dogQue.isEmpty()){
if(dogQue.peek().getCount() > catQue.peek().getCount()){
return catQue.poll().getPet();
}else{
return dogQue.poll().getPet();
}
} else if(catQue.isEmpty()){
return dogQue.poll().getPet();
} else if(dogQue.isEmpty()){
return catQue.poll().getPet();
}else{
throw new RuntimeException("this DogCatQueue is empty");
}
}
public Dog pollDog(){
if(dogQue.isEmpty()){
throw new RuntimeException("dog queue is empty");
}else{
return (Dog)dogQue.poll().getPet();
}
}
public Cat pollCat(){
if(catQue.isEmpty()){
throw new RuntimeException("cat queue is empty");
}else{
return (Cat)catQue.poll().getPet();
}
}
public boolean isEmpty(){
return catQue.isEmpty() && dogQue.isEmpty();
}
public boolean isDogEmpty(){
return dogQue.isEmpty();
}
public boolean isCatEmpty(){
return catQue.isEmpty();
}
}
7. 剑指 Offer 59 - II. 队列的最大值
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1
解题思路
定义两个队列,一个是queue,存放所有元素,另一个是helper,只存放最大值。push_back 时直接将元素放到queue中,如果helper为空的话,直接放入,不为空,就要将队列尾部小于此元素的元素全部移出,之后,将此元素放入队列尾部,这样,才能保证最大值。pop_front 时,如果queue中的队首元素和helper中的队首元素相等,就将helper队首元素移出,注意,这里要用equals,具体原因,可以参考。
class MaxQueue {
Deque<Integer> queue;
Deque<Integer> helper;
public MaxQueue() {
queue = new LinkedList<>();
helper = new LinkedList<>();
}
public int max_value() {
if(helper.isEmpty()) return -1;
return helper.peek();
}
public void push_back(int value) {
queue.offer(value);
//判断队列的另一端是否有小于value的,有的话,全部移出
while(!helper.isEmpty() && value > helper.peekLast()) {
helper.pollLast();
}
helper.offer(value);
}
public int pop_front() {
if(queue.isEmpty()) return -1;
if(helper.peek().equals(queue.peek())) {
helper.pop();
}
return queue.poll();
}
}
如有不足之处,欢迎指正,谢谢!