刷题笔记(十)--栈和队列:基础题目

系列文章目录

刷题笔记(一)–数组类型:二分法
刷题笔记(二)–数组类型:双指针法
刷题笔记(三)–数组类型:滑动窗口
刷题笔记(四)–数组类型:模拟
刷题笔记(五)–链表类型:基础题目以及操作
刷题笔记(六)–哈希表:基础题目和思想
刷题笔记(七)–字符串:经典题目
刷题笔记(八)–双指针:两数之和以及延伸
刷题笔记(九)–字符串:KMP算法

前言

这一篇博客是关于栈和队列的基本使用的,也是基础的一些操作。

这里关于栈的性质主要就是是一个:先进先出。具体的可以看我这篇博客

数据结构–栈

如果感觉再看比较麻烦的话,那就看下面的这个图吧。
在这里插入图片描述

队列

关于队列的性质:先进后出。具体的可以看我这篇博客

数据结构–队列

其中包括了我们普通的队列:

在这里插入图片描述

循环队列:

在这里插入图片描述

当然还有双端队列。这里双端队列就不给图了,双端队列和普通的队列主要的差别就是:双端队列都可以入队和出队。

题录

232. 用栈实现队列

题目链接如下;

232. 用栈实现队列

题目截屏如下:
在这里插入图片描述
这道题思路其实很简单,就是用两个栈来回的倒腾。但是呢,仔细想,这个倒腾就很有学问。如果说方式不对的话,那么就会耗费很多功夫。

public class 用栈实现队列 {
    Stack<Integer> a;
    Stack<Integer> b;

    public 用栈实现队列() {
        a = new Stack();//用来入队
        b = new Stack();//用来出队
    }

    public void push(int x) {
        a.push(x);
    }

    public int pop() {
        while(b.empty()){
            while(!a.empty()){
                b.push(a.pop());
            }
        }
        return b.pop();
    }

    public int peek() {
        while(b.empty()){
            while(!a.empty()){
                b.push(a.pop());
            }
        }
        return b.peek();
    }

    public boolean empty() {
        return a.empty() && b.empty();
    }
}

225. 用队列实现栈

题目链接如下:

225. 用队列实现栈

题目截图如下:
在这里插入图片描述
其实还是一个倒腾的过程,和上面的题一样的思想。

public class 用队列实现栈 {
    Queue<Integer> a = null;//用来入栈
    Queue<Integer> b = null;//用来出栈
    public 用队列实现栈() {
        a = new LinkedList<>();
        b = new LinkedList<>();
    }

    public void push(int x) {
        //push方法其实就是注意一个地方,你要保证a永远为null
        a.offer(x);
        while(!b.isEmpty()){
            a.offer(b.poll());
        }
        Queue tmp = a;
        a = b;
        b = tmp;
    }

    public int pop() {
        return b.poll();
    }

    public int top() {
        return b.peek();
    }

    public boolean empty() {
        return b.isEmpty();
    }
}

20. 有效的括号

题目链接如下:

20. 有效的括号

题目截图如下:
在这里插入图片描述
这个题就是一个很基础的对于栈的特性考察的题目,如果是左括号就入栈,是右括号就判断要不要出栈,或者说是报错。

public class 有效的括号 {
    public boolean isValid(String s) {
        //定义一个栈用来判断括号是否匹配
        Stack<Character> stack = new Stack<>();
        char[] arr = s.toCharArray();
        for (int i = 0; i < arr.length; i++) {
            //如果是左括号统一入栈
            if (arr[i] == '(' || arr[i] == '[' || arr[i] == '{') {
                stack.push(arr[i]);
                continue;
            } else {
                //如果不是左括号,判断是否栈空,如果空就直接报错,因为匹配不上了
                if(stack.empty()){
                    return false;
                }
                char tmp = stack.peek();
                //不空的话就进行是否匹配的判断,不匹配也报错,因为又匹配不上了
                if ((arr[i] == ']' && tmp == '[') ||
                        (arr[i] == ')' && tmp == '(') ||
                        (arr[i] == '}' && tmp == '{')) {
                    stack.pop();
                }else {
                    return false;
                }
            }
        }
        //最后如果是栈空,那么才证明匹配
        return stack.empty();
    }
}

1047. 删除字符串中的所有相邻重复项

题目链接如下:

1047. 删除字符串中的所有相邻重复项

题目截图如下:
在这里插入图片描述
emmmm,这个题吧,怎么说呢,两种解法,第一种解法:双指针法

public class 删除字符串中所有的相邻重复项__双指针法 {
    public String removeDuplicates(String s) {
        char[] arr = s.toCharArray();
        int i = -1; //默认(前边数据,i)是已经处理好的数据
        int j = 0;//j用来遍历
        while(j < arr.length){
            //如果说重复了,那么就说明当前i下标的值我们不需要,回退就好
            if(i >= 0 && arr[i] == arr[j]){
                i--;
            }else{
                //如果不重复,那就说明需要,暂时先进行赋值
                i++;
                arr[i] = arr[j];
            }
            j++;
        }
        //现在(0,i]都是不重复的了
        return String.copyValueOf(arr,0,i+1);
    }
}

第二种方法:栈

public class 删除字符串中所有的相邻重复项__栈的使用 {
    public static String removeDuplicates(String s) {
        char[] arr = s.toCharArray();
        //用一个栈来实现题目要求
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < arr.length; i++) {
            //如果说栈为空或者说栈顶元素和当前下标元素不同就入栈
            if(stack.empty() || stack.peek() != arr[i]){
                stack.push(arr[i]);
            }else{//相同就出栈
                stack.pop();
            }
        }
        //最后定义一个字符拼接器来接收结果
        StringBuilder str = new StringBuilder();
        while(!stack.empty()) str.append(stack.pop());
        //接收完之后返回的时候别忘了反转一下
        return str.reverse().toString();
    }

    public static void main(String[] args) {
        String a = "abbaca";
        System.out.println(removeDuplicates(a));
    }
}

150. 逆波兰表达式求值

题目链接如下:

150. 逆波兰表达式求值

题目截图如下:

在这里插入图片描述

还是对于栈的性质的运用

public class 逆波兰表达式 {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for (int i = 0; i < tokens.length; i++) {
            String s = tokens[i];
            if(isNumber(s)){
                //如果不是运算符,就直接入栈就行
                stack.push(Integer.valueOf(s));
            }else{
                //如果是运算符,出栈两个元素,进行运算
                int a = stack.pop();
                int b = stack.pop();
                //这里注意,b在前,a在后
                if(s.equals("+")) stack.push(a + b);
                if(s.equals("-")) stack.push(b - a);
                if(s.equals("*")) stack.push(a * b);
                if(s.equals("/")) stack.push(b / a);
            }
        }
        return stack.pop();
    }
    //判断一下当前字符是不是一个数字
    public boolean isNumber(String s){
        return !((s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")));
    }
}

239. 滑动窗口最大值

题目链接如下:

239. 滑动窗口最大值

题目截图如下:
在这里插入图片描述

这道题有两种解法,一种就是暴力遍历,每一个窗口当中都有最大值
在这里插入图片描述

一般来说,我们确实可以通过这种方法求解,笔者第一时间也是这样子解决的,这样解决的时间复杂度是O(m * n),但是嗷,这样有一个很大的问题,就是当这个数组很大很大的时候,这个时候恰好k也不小,那么时间复杂度就很大,就会超出时间的限制。其中有一个测试用例就是50000大小的滑动窗口,这简直就是很离谱。
所以我们这里要用另一种方法来解决,也就是单调队列这种思想。先上代码

public class 滑动窗口最大值 {
    public int[] maxSlidingWindow(int[] nums, int k) {
        Deque<Integer> dq = new LinkedList<>();//定义一个双端链表用来筛选出最大值
        int len = nums.length;
        int[] arr = new int[len - k + 1];//定义一个刚好可以完全接收当前窗口最大值的数组
        for (int i = 0; i < len; i++) {
            //头:首先就是判断一下当前窗口的大小,如果说当前窗口大于k了,就要丢掉队头的元素
            if(!dq.isEmpty() && i - k + 1 > dq.getFirst()){
                dq.poll();//如果说当前的窗口值大于k,就丢弃掉头元素
            }
            //尾:如果说当前要入队的下标对应数组元素,大于当前队尾下标对应数组元素,就把这个队尾元素删除
            while(!dq.isEmpty() && nums[i] > nums[dq.getLast()]){
                dq.pollLast();
            }
            //尾:现在这么一搞之后,就可以确定队首元素对应的下标值为最大
            dq.add(i);
            //头:让arr来记录当前窗口最大值
            if(i >= k - 1){
                arr[i - k + 1] = nums[dq.getFirst()];
            }
        }
        return arr;
    }
}

这里主要说明一个事情,就是双端队列dq里面存的是数组的下标,是下标,是下标,重要的事情说三遍!!!不是存的元素。为什么要存下标呢?

存下标原因有两个:1.可以通过下标直接操作或者表示数组元素。 2.用下标来存储的话该队列是一个单调队列,可以很轻易的判断出当前滑动窗口的大小。

笔者在这里改了又改,删了又删,最后还是没有找到一个比较合理的方式来进行讲解。先停一下吧这里,笔者的解题思路其实已经放在了代码当中,其中需要注意的地方也进行了说明。这里的话如果后面能找到合适的方式去讲解的话,笔者再把这里补上。

总结

其实没有什么特别需要说的,都是一些比较基础的题目,加油吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值