栈和队列问题一

本文介绍了如何使用固定大小的数组实现栈和队列,包括基本操作如压栈、弹栈、入队、出队等。此外,还展示了如何设计一个能在O(1)时间内返回栈中最小元素的栈,以及如何仅用队列或栈结构实现另一种数据结构。最后,提出了猫狗队列的问题,通过维护两个队列解决不同类型的宠物按顺序出队的需求。
摘要由CSDN通过智能技术生成

一 用固定的大小的数组实现栈和队列

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();
    }
}

关于猫狗队列问题,可以好好体会下设计的思想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值