算法题解 —— 栈(1-5)

本文详细介绍了使用Java实现的几个栈相关的算法题解,包括设计一个带有getMin功能的栈、仅用递归和栈操作逆序栈、猫狗队列、用一个栈实现对另一个栈的排序以及栈解决汉诺塔问题。通过这些题目,展示了栈在数据结构和算法中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内容来源于自己的刷题笔记,对一些题目进行方法总结,用 java 语言实现。

第一章 栈和队列

1. 设计一个有 getMin 功能的栈:
  1. 题目描述:

    实现一个特殊的栈,在实现栈的基础上,再实现返回栈中最小元素的操作。

  2. 解题思路:

    在设计时,我们使用两个栈,一个栈用来保存当前栈中的元素,其功能和一个正常的栈没有区别,这个栈记为 stackData;另一个栈记为 stackMin。具体的实现方式有两种:(一般情况下)

    1. 数据压栈时,先对 stackMin 进行判断,如果小于等于栈顶元素,压入 stackMin 和 stackData 中,反之则只压入 stackMin 中;数据出栈时,要比较此时两个栈顶元素是否相等,如果相等,stackMin 也得出栈。这样设计能保证 stackMin 内的元素自上而下非严格递增,并且栈的大小小于 stackData,因此在判断是否为空时只需要判断其中之一就可。
    2. 数据压栈时,先对 stackMin 进行判断,如果小于等于栈顶元素,压入 stackMin 和 stackData 中,反之将当前 stackMin 的栈顶元素再次压入一次;数据出栈时两个栈同时出栈。

    这两个方法都是用 stackMin 栈保存着 stackData 每一步的最小值。共同点是所有操作的时间复杂度都是 O(1),空间复杂度都是 O(n)。区别是:第一个方法中 stackMin 压入栈时稍省空间,但是弹出操作费时间;第二个方法中 stackMin 压入栈时费空间,但是弹出操作稍省时间。

  3. 代码实现:

    public class MyStack1 {
    
        Stack<Integer> stackData;
        Stack<Integer> stackMin;
    
        public MyStack1(){
            stackData = new Stack<>();
            stackMin = new Stack<>();
        }
    
        public void push(int newNum){
            if (stackMin.isEmpty()){
                stackMin.push(newNum);
            }else if (stackMin.peek() >= newNum){
                stackMin.push(newNum);
            }
            stackData.push(newNum);
        }
    
        public int pop(){
            if (stackData.isEmpty()){
                throw new RuntimeException("Your stack is empty");
            }
            int value = stackData.pop();
            if (stackMin.peek() == value){
                stackMin.pop();
            }
            return value;
        }
    
        public int getMin(){
            if (stackData.isEmpty()){
                throw new RuntimeException("Your stack is empty");
            }
            return stackMin.peek();
        }
    }
    
    public class MyStack2 {
    
        Stack<Integer> stackData;
        Stack<Integer> stackMin;
    
        public MyStack2(){
            stackData = new Stack<>();
            stackMin = new Stack<>();
        }
    
        public void push(int newNum){
            if (stackMin.isEmpty()){
                stackMin.push(newNum);
            }else if (stackMin.peek() >= newNum){
                stackMin.push(newNum);
            }else {
                stackMin.push(stackMin.peek());
            }
            stackData.push(newNum);
        }
    
        public int pop(){
            if (stackData.isEmpty()){
                throw new RuntimeException("Your stack is empty");
            }
            stackMin.pop();
            return stackData.pop();
        }
    
        public int getMin(){
            if (stackData.isEmpty()){
                throw new RuntimeException("Your stack is empty");
            }
            return stackMin.peek();
        }
    }
    
2.如何仅用递归函数和栈操作逆序一个栈:
  1. 题目描述:

    一个栈依次压入 1、2、3、4、5,那么从栈顶到栈底分别为 5、4、3、2、1。将这个栈转置后,从栈顶到栈底为 1、2、3、4、5,也就是实现栈中元素的逆序,但是只能用递归函数来实现,不能用其他数据结构。

  2. 解题思路:

    我们需要设计两个递归函数,一个用于获取栈底元素并且将其删除,另外一个就是转置的主函数。在转置的递归调用过程中,不断得将栈底元素删除,再将其压入栈顶。

  3. 代码实现:

        /**
         * 将栈 stack 的栈底元素返回并移除
         * @param stack
         * @return
         */
        private static int getAndRemoveLastElement(Stack<Integer> stack){
            int result = stack.pop();
            if (stack.isEmpty()){
                return result;
            }else {
                int last = getAndRemoveLastElement(stack);
                stack.push(result);
                return last;
            }
        }
    
        /**
         * 逆序一个栈
         * @param stack
         */
        public static void reverse(Stack<Integer> stack){
            if (stack.isEmpty()){
                return;
            }
            int i = getAndRemoveLastElement(stack);
            reverse(stack);
            stack.push(i);
        }
    
3. 猫狗队列:
  1. 题目描述:

    宠物,狗和猫的类如下:

    public class Pet {
    
        private String type;
    
        public Pet(String type){
            this.type = type;
        }
    
        public String getType(){
            return type;
        }
    }
    
    public class Dog extends Pet{
    
        public Dog(String type) {
            super("Dog");
        }
    }
    
    public class Cat extends Pet{
    
        public Cat(String type) {
            super("Cat");
        }
    }
    

    实现一种猫狗队列的结构,要求如下:

    • 用户可以调用 add 方法将 cat 类或 dog 类的实例放入队列中
    • 用户可以调用 pollAll 方法,将队列中所有的实例按照队列的先后顺序依次弹出。
    • 用户可以调用 pollDog 方法,将队列中 dog 类的实例按照队列的先后顺序依次弹出。
    • 用户可以调用 pollCat 方法,将队列中 cat 类的实例按照队列的先后顺序依次弹出。
    • 用户可以调用 isEmpty 方法,检查队列中是否还有 dog 或 cat 的实例。
    • 用户可以调用 isDogEmpty 方法,检查队列中是否还有 dog 的实例。
    • 用户可以调用 isCatEmpty 方法,检查队列中是否还有 cat 的实例。
  2. 解题思路:

    本题实现将不同的实例盖上时间戳的方法,但是又不能改变用户本身的类,所以定义一个新的类 PetEnterQueue,属性包括实例和一个计数值(也就是插入队列的序号值)。

    具体的猫狗队列实现逻辑为:

    • 在加入实例时,如果实例是 dog,就盖上时间戳,生成对应的 PetEnterQueue 类的实例,然后放入 dogQ;如果实例是 cat,就盖上时间戳,生成对应的 PetEnterQueue 类的实例,然后放入 catQ 中。
    • 弹出方法只需要先比较两个队列的队列头元素序号,小的先弹出(如果此时两个队列都不为空)。
    • 判断是否有实例方法实现较为简单,本身队列就有类似的实现方法,只需要调用即可。
  3. 代码实现:

    /**
     * @Author: milkteazzz
     * @Data: 2020-12-19 10:12
     * @Version: 1.0
     *
     * 宠物类中间队列,属性包含实例以及数量(这里是序号的意思,用来标明顺序用)
     */
    public 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 String getEnterType(){
            return pet.getType();
        }
    }
    
    /**
     * @Author: milkteazzz
     * @Data: 2020-12-19 10:24
     * @Version: 1.0
     *
     * 猫狗队列的具体实现
     */
    public class CatDogQueue {
    
        private Queue<PetEnterQueue> dogQ;
        private Queue<PetEnterQueue> catQ;
        private long count;
    
        public CatDogQueue() {
            dogQ = new LinkedList<>();
            catQ = new LinkedList<>();
            count = 0;
        }
    
        public void add(Pet pet){
            if (pet.getType().equals("Cat")){
                catQ.add(new PetEnterQueue(pet,count++));
            }else if (pet.getType().equals("Dog")){
                dogQ.add(new PetEnterQueue(pet,count++));
            }else {
                throw new RuntimeException("error,not cat or dog");
            }
        }
    
        public Pet pollAll(){
            if (!catQ.isEmpty() && !dogQ.isEmpty()){
                if (catQ.peek().getCount() < dogQ.peek().getCount()){
                    return catQ.poll().getPet();
                }else {
                    return dogQ.poll().getPet();
                }
            }else if (!catQ.isEmpty()){
                return catQ.poll().getPet();
            }else if (!dogQ.isEmpty()){
                return dogQ.poll().getPet();
            }else {
                throw new RuntimeException("error,queue is empty");
            }
        }
    
        public Dog pollDog(){
            if (!dogQ.isEmpty()){
                return (Dog) dogQ.poll().getPet();
            }
            throw new RuntimeException("error,dog queue is empty");
        }
    
        public Cat pollCat(){
            if (!catQ.isEmpty()){
                return(Cat) catQ.poll().getPet();
            }
            throw new RuntimeException("error,cat queue is empty");
        }
    
        public boolean isEmpty(){
            return catQ.isEmpty() || dogQ.isEmpty();
        }
    
        public boolean isCatEmpty(){
            return catQ.isEmpty();
        }
    
        public boolean isDogEmpty(){
            return dogQ.isEmpty();
        }
    }
    
4. 用一个栈实现对另外一个栈的排序:
  1. 题目描述:

    一个栈中元素的类型为整型,现在想将该栈从顶到底按从大到小的顺序排序,只允许申请一个栈。除此之外,可以申请新的变量,但不能申请额外的数据结构。

  2. 解题思路:

    首先有两个栈,需要排序的栈 stack 和辅助栈 help。

    主要思路是:将 stack 中的元素压入 help 的过程中,进行排序压入 help 中,最终再循环压回 stack。

    stack 的要求是从顶部到底部是从大到小,而 help 是从小到大。

    首先先将 stack 的元素弹出,但是不一定它会立刻压入栈中,于是用一个变量将其存储起来,命名为 cur。于是将 cur 与 help 的栈顶元素进行比较,如果是大于栈顶元素,必须将其压到栈顶以下的位置。这个时候,需要将 help 中小于 cur 的元素压回 stack 中,然后将 cur 压入栈,再将元素压回来,这个时候 stack 是 help 的辅助栈。

  3. 代码实现:

    public static void sortStackByStack(Stack<Integer> stack){
        Stack<Integer> help = new Stack<>();
        while (!stack.isEmpty()){
            int cur = stack.pop();
            while (!help.isEmpty() && cur > help.peek()){
                stack.push(help.pop());
            }
            help.push(cur);
        }
        while (!help.isEmpty()){
            stack.push(help.pop());
        }
    }
    
5. 用栈来实现汉诺塔问题:
  1. 问题描述:

    修改一下规则:现在限制不能从最左侧的塔直接移动到右侧,也不能从最右侧直接移动到最左侧,而是必须经过中间。求当塔有 N 层的时候,打印移动过程和最优移动总步数。

  2. 解题思路:

    塔不能从 “左” 移动到 “右”,也不能从 “右” 移动到 “左”,而是要经过中间过程。也就是说,实际动作只有4个:“左” 到 “中”,“中” 到 “左”,“中” 到 “右”,“右” 到 “中”。

    现在我们把左,中,右三个地点抽象成栈,依次记为 LS、MS、RS。最初所有的塔都在 LS 上。那么如上 4 个动作就能看作是:某一个栈(from)把栈顶元素弹出,然后压入到另一个栈里(to),作为这一个栈(to)的栈顶。

    这里需要注意有两个原则,这也是需要注意的逻辑:

    • 小压大原则:如果将 from 的栈顶元素移动到 to 中,必须保证该元素小于 to 的栈顶元素。
    • 不可逆原则:由于小于找到最短的移动过程,即不能出现从 L 到 M,然后下一步又从 M 移动到 L。
    • 不可重复原则:由于本身汉诺塔的结构就是 “头轻脚重”,一旦我们将 L 的栈顶移动到 M 中,此时 M 的栈顶元素小于 L 的栈顶元素,M 不可能再一次将元素压到 M 中。
  3. 代码实现:

    public class StackHanoiProblem {
    
        public enum Action{
            No,LToM,MToL,MToR,RToM;
        }
    
        public int hanoiProblem(int num,String left,String mid,String right){
            Stack<Integer> lS = new Stack<>();
            Stack<Integer> mS = new Stack<>();
            Stack<Integer> rS = new Stack<>();
            lS.push(Integer.MAX_VALUE);
            mS.push(Integer.MAX_VALUE);
            rS.push(Integer.MAX_VALUE);
            for(int i = num;i > 0;i--){
                lS.push(i);
            }
            Action[] record = {Action.No};
            int step = 0;
            while (rS.size() != num + 1){
                //由于不可重复和不可逆原则,一个动作的下一步动作只可能对应两个
                step += fStackForStack(record,Action.MToL,Action.LToM,lS,mS,left,mid);
                step += fStackForStack(record,Action.LToM,Action.MToL,mS,lS,mid,left);
                step += fStackForStack(record,Action.RToM,Action.MToR,mS,rS,mid,right);
                step += fStackForStack(record,Action.MToR,Action.RToM,rS,mS,right,mid);
            }
            return step;
        }
    
        public static int fStackForStack(Action[] record,Action preNoAct,Action nowAck,
                                         Stack<Integer> fStack, Stack<Integer> tStack,String from,String to){
            //上一步与这一步不能重复,并且不能大压小
            if (record[0] != preNoAct && fStack.peek() < tStack.peek()){
                tStack.push(fStack.pop());
                System.out.println("Move "+tStack.peek()+" from "+from+" to "+to);
                record[0] = nowAck;
                return 1;
            }
            return 0;
        }
    }
    
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值