一 用固定的大小的数组实现栈和队列
1.固定大小的数组实现栈结构
package com.offer.class3;
/**
* 固定数组实现栈结构
*/
public class StackWithArray {
private int[] arr;
private int index; // 指向即将放入元素的位置
public StackWithArray(int initialSize){
if(initialSize < 0){
throw new IllegalArgumentException("the init size is less than 0");
}
arr = new int[initialSize];
index = 0;
}
// 压栈
public void push(int obj){
if(index == arr.length){
throw new ArrayIndexOutOfBoundsException("the stack is full!");
}
arr[index++] = obj; // index指向的就是当前要存储数据的位置
}
// 弹栈(删除元素)
public int pop(){
if(index == 0){
throw new ArrayIndexOutOfBoundsException("the stack is empty!");
}
return arr[--index]; // 删除的是index指向的前一个元素,因为index指向的是位置为空
}
// 弹出元素,但不删除
public int peek(){
if(index == 0){
throw new ArrayIndexOutOfBoundsException("the stack is empty!");
}
return arr[index - 1]; // index并没有减小,所以index位置上的元素并没有删除
}
}
2 固定大小的数组实现队列
注意:start、end、size 这三个变量的实际意义,size 变量实现了对 start 和 end 变量之间的解耦。
package com.offer.class3;
/**
* 固定数组实现队列
* 三个变量:start、end、size
*/
public class QueueWithArray {
private int start; // 指向队头,每次要取数据的位置
private int end; // 指向队尾,每次要添加数据的位置
private int size; // 队列中元素的个数,利用size实现start和end之间的解耦
private int[] arr;
public QueueWithArray(int initialSize){
if(initialSize < 0){
throw new IllegalArgumentException("the initialSzie is less than 0");
}
arr = new int[initialSize];
start = 0;
end = 0;
size = 0;
}
// 添加一个元素
public void push(int obj){
if(size == arr.length){
throw new ArrayIndexOutOfBoundsException("the queue is full");
}
size++;
arr[end] = obj;
// 如果end指向数组中最后一个元素的位置,那么需要跳到开始的位置,从头开始
end = (end == arr.length - 1) ? 0 : end + 1;
}
public int poll(){
if(size == 0){
throw new ArrayIndexOutOfBoundsException("the queue is empty");
}
size--;
int tmp = start;
start = (start == arr.length - 1) ? 0 : start + 1;
return arr[tmp];
}
}
二 能返回栈中最小元素的栈
实现一个特殊的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作。
要求:
1.pop、push、getMin 操作的时间复杂度都是O(1)。
2.设计的栈类型可以使用现成的栈结构。
思路:用一个额外的栈存储最小元素。
package com.offer.class3;
import java.util.Stack;
/**
* 用一个额外的栈空间维持一个最小元素
*/
public class MyStack {
private Stack<Integer> dataStack;
private Stack<Integer> minStack;
public MyStack(){
dataStack = new Stack<Integer>();
minStack = new Stack<Integer>();
}
public void push(int obj){
dataStack.push(obj);
if(minStack.isEmpty()){
minStack.push(obj); // 当最小值栈为空时,直接将数存进去
}else if(obj <= minStack.peek()){
minStack.push(obj); // 当obj小于等于最小值栈中的最小值时,直接压入栈中
}else{
minStack.push(minStack.peek()); // 将最小值栈中的最小值再压入一遍
}
}
public int pop(){
minStack.pop();
return dataStack.pop();
}
public int getMin(){
if(minStack.isEmpty()){
throw new ArrayIndexOutOfBoundsException("the stack is empty!");
}
return minStack.peek();
}
}
三 如何仅用队列结构实现栈结构
原理:可以用两个队列(queue、help)来实现栈,加元素时加总是在queue;删除元素时,把 queue 最后一位前的元素全部弹出放入 help 队列中,然后再弹出返回 queue 的最后一位元素(这就达成栈后入先出的要求了),然后交换 help 和queue 指针即可
队列:poll(移除并返回队列的头部),add(添加一个元素到队列尾部),peek(返回队列的头部,不删除)
package com.offer.class3;
import java.util.LinkedList;
import java.util.Queue;
/**
* 用两个栈实现队列
*/
public class TwoQueueWithStack {
private Queue<Integer> queue;
private Queue<Integer> help;
public TwoQueueWithStack(){
// LinkedList实现了Queue接口
queue = new LinkedList<Integer>();
help = new LinkedList<Integer>();
}
// 插入一个元素
public void push(int obj){
// 插入元素永远都是插入到queue中
queue.add(obj);
}
// 删除一个元素
public int pop(){
if(queue.isEmpty()){
throw new RuntimeException("stack is empty!");
}
while(queue.size() > 1){
// 将queue中除最后一个元素外,全部弹出添加到help中
help.add(queue.poll());
}
int res = queue.poll();
swap();
return res;
}
// 弹出一个元素(不删除)
public int peek(){
if(queue.isEmpty()){
throw new RuntimeException("stack is empty!");
}
while(queue.size() > 1){
// 将queue中除最后一个元素外,全部弹出添加到help中
help.add(queue.poll());
}
int res = queue.poll();
help.add(res);
swap();
return res;
}
// 互换queue和help的指针,help只是辅助队列,始终操作的还是queue
public void swap(){
Queue<Integer> temp = help;
help = queue;
queue = temp;
}
}
四 如何仅用栈结构实现队列结构?
原理:可以用两个栈(stack1和stack2)来实现队列 ,进入时放入stack1栈,出栈时从stack2栈出,这样就能把顺序变为先进先出,( 栈:push,pop,peek)
需要注意的点:
1、只有当stack2为空时,stack1才能往stack2中放数据,不然顺序就会乱了;
2、如果stack1要往stack2中放数据,肯定是一次性将stack1中的数据全部放到stack2中。
package com.offer.class3;
import java.util.Stack;
/**
* 用两个栈实现队列
*/
public class TwoStackWithQueue {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
// 添加元素
public void add(int obj){
stack1.push(obj);
}
// 删除元素
public int poll(){
if(stack2.isEmpty() && stack1.isEmpty()){
throw new RuntimeException("queue is empty!");
}else if(stack2.isEmpty()){
while(!stack1.isEmpty()){
// stack2如果为空,则stack1中的元素全部倒进stack2中
stack2.push(stack1.pop());
}
}
// 如果stack2中有元素,则直接弹出。只有当stack2为空时,才会从stack1中往stack2中放数据,而且肯定是一次性放完
return stack2.pop();
}
// 弹出元素,不删除
public int peek(){
if(stack2.isEmpty() && stack1.isEmpty()){
throw new RuntimeException("queue is empty!");
}else if(stack2.isEmpty()){
while(!stack1.isEmpty()){
// stack2如果为空,则stack1中的元素全部倒进stack2中
stack2.push(stack1.pop());
}
}
// 弹出stack2中最上面的元素,即实现了队列的先进先出
return stack2.peek(); // 前面的和poll一样,只不过最后需要返回而不是删除
}
}
五 猫狗队列
宠物、狗和猫的类如下:
public class Pet { private String type; }
public Pet(String type) { this.type = type; }
public String getPetType() { return this.type; }
public class Dog extends Pet { public Dog() { super("dog"); } }
public class Cat extends Pet { public Cat() { super("cat"); } }
实现一种狗猫队列的结构,要求如下:
用户可以调用add方法将cat类或dog类的实例放入队列中;
用户可以调用pollAll方法,将队列中所有的实例按照进队列的先后顺序依次弹出;
用户可以调用pollDog方法,将队列中dog类的实例按照进队列的先后顺序依次弹出;
用户可以调用pollCat方法,将队列中cat类的实例按照进队列的先后顺序依次弹出;
用户可以调用isEmpty方法,检查队列中是否还有dog或cat的实例;
用户可以调用isDogEmpty方法,检查队列中是否有dog类的实例;
用户可以调用isCatEmpty方法,检查队列中是否有cat类的实例。
分析:
1、建一个猫狗队列类,这个类里包含 DogQueue 和 CatQueue 两个队列,用于分别加猫和加狗,但这个会导致在pollAll时无法判断之前猫狗的顺序,所以有了第2步;
2、建一个 CatDog 类,里面包含 pet 和 count 两个成员,pet 用于记录 CatDog 类的这个实例是猫还是狗,count 用于记录当前 pet 的顺序,count小的在前面,那么就能判断猫狗的顺序了。
package com.offer.class3;
import java.util.LinkedList;
import java.util.Queue;
/**
* 猫狗队列问题
*/
public class CatDogQueue {
public static class Pet{
private String type;
public Pet(String type){
this.type = type;
}
public String getPetType(){
return this.type;
}
}
public static class Dog extends Pet{
public Dog(){
super("dog");
}
}
public static class Cat extends Pet{
public Cat(){
super("cat");
}
}
public static class CatDog{
private Pet pet; // 用于记录是猫还是狗
private long count; // 用于记录当前pet的顺序
public CatDog(Pet pet, long count){
this.pet = pet;
this.count = count;
}
public Pet getPet(){
return this.pet;
}
public long getCount(){
return this.count;
}
}
// CatDogQueue 的正式代码
private Queue<CatDog> catQueue = new LinkedList<CatDog>(); // 猫队列
private Queue<CatDog> dogQueue = new LinkedList<CatDog>(); // 狗队列
private long count = 0;
// 增加元素
public void add(Pet pet){
if(pet.getPetType().equals("cat")){
catQueue.add(new CatDog(pet, count++));
}else if(pet.getPetType().equals("dog")){
dogQueue.add(new CatDog(pet, count++));
}else{
throw new RuntimeException("error : this is not cat or dog type!");
}
}
// 弹出所有元素
public Pet pollAll(){
if(!catQueue.isEmpty() && !dogQueue.isEmpty()){
if(catQueue.peek().count < dogQueue.peek().count){
// 如果猫队列的第一个元素顺序在狗队列第一个元素之前,则弹出猫队列第一个元素
return catQueue.poll().getPet();
}else{
return dogQueue.poll().getPet();
}
}else if(!catQueue.isEmpty()){
return catQueue.poll().getPet();
}else if(!dogQueue.isEmpty()){
return dogQueue.poll().getPet();
}else{
throw new RuntimeException("error : queue is empty!");
}
}
// 弹出狗猫列元素
public Cat pollCat(){
if(catQueue.isEmpty()){
throw new RuntimeException("error : the cat queue is empty!");
}else{
return (Cat) catQueue.poll().getPet();
}
}
// 弹出狗队列元素
public Dog pollDog(){
if(dogQueue.isEmpty()){
throw new RuntimeException("error : the dog queue is empty!");
}else{
return (Dog) dogQueue.poll().getPet();
}
}
public boolean isAllEmpty(){
return dogQueue.isEmpty() && catQueue.isEmpty();
}
public boolean isDogQueueEmpty(){
return dogQueue.isEmpty();
}
public boolean isCatQueueEmpty(){
return catQueue.isEmpty();
}
}
关于猫狗队列问题,可以好好体会下设计的思想。