栈和队列入门,小白看过来!!

栈和队列

介绍:

栈(Stack)和队列(Queue)是计算机科学中常用的数据结构,它们用于管理和组织数据。

栈(Stack):
  1. 定义: 栈是一种线性数据结构,它遵循 Last In, First Out(LIFO)原则,即最后进入栈的元素是第一个被移除的。
  2. 操作:
    • 压入(Push): 将元素添加到栈的顶部。
    • 弹出(Pop): 从栈的顶部移除元素。
    • 查看栈顶元素(Top): 获取但不移除栈顶的元素。
  3. 应用:
    • 用于实现递归算法的调用栈。
    • 表达式求值。
    • 浏览器的前进和后退功能。
  4. 实现: 可以使用数组或链表实现栈。
队列(Queue):
  1. 定义: 队列是一种线性数据结构,它遵循 First In, First Out(FIFO)原则,即最先进入队列的元素是第一个被移除的。
  2. 操作:
    • 入队(Enqueue): 将元素添加到队列的尾部。
    • 出队(Dequeue): 从队列的头部移除元素。
    • 查看队头元素(Front): 获取但不移除队列头部的元素。
  3. 应用:
    • 任务调度。
    • 广度优先搜索算法。
    • 打印队列。
  4. 实现: 可以使用数组或链表实现队列,也有特殊类型如双端队列(Deque)。

两者特点:

  1. 限制访问: 栈和队列都限制了数据的访问方式,栈只允许从顶部访问,队列只允许从前端访问。
  2. 实现: 可以使用数组或链表等数据结构来实现栈和队列。
  3. 基本操作: 压入/入队、弹出/出队、查看栈顶/队头元素等是它们的基本操作。

image-20231125082035890image-20231125082043666

入门经典示例:

有效的括号20

image-20231125082227383

基本思路l:

遍历字符串,当遇到左括号时,将其推入栈中;当遇到右括号时,检查栈顶元素是否是相匹配的左括号,如果是,则将栈顶元素弹出,否则返回 false。最后,如果栈为空,说明所有括号都匹配成功,返回 true;否则,返回 false。

class Solution {

    public boolean isValid(String s) {
        // 当字符串长度为奇数的时候,属于无效情况
        // 条件说明了长度至少为 1,所以不需要在判空
        if (s.length() % 2 == 1) {
            return false;
        }

        //构建栈
        Stack<Character> stack = new Stack<Character>();

        //由左向右遍历字符串
        for(char c : s.toCharArray()){
               
            if(c == '('){
                stack.push(')');
            }else if(c == '['){
                stack.push(']');
            }else if( c == '{'){
                stack.push('}');
               //列举三种左括号情况,当是左括号时直接压入栈中
            }else if( stack.isEmpty() || c != stack.pop()){
            //除去左括号的情况,当最新的字符不等于刚放入栈中的元素时,返回false,
            //当并没有匹配,而右侧直接是空时直接结束;表明有多余的右括号
            //其实省略了一种情况,即c=stack.pop()时,表明正常,不进行任何操作;
                return false;
            }
            
        }
        //当完美运行完毕后,栈肯定为空
        return stack.isEmpty();
    }
}

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

使用java来写

想象一下入栈字符串,之后每个字符出栈都需要先和上一个比较是否相同,若相同两者都消去,不同入栈

方法一:使用可变字符串直接模拟栈来操作节省空间和时间

class Solution {
    public String removeDuplicates(String s) {
        // 将 res 当做栈
        // 也可以用 StringBuilder 来修改字符串,速度更快
        // StringBuilder res = new StringBuilder();
        StringBuffer res = new StringBuffer();      //StringBuffer 指可变性字符串类型,初始长度为0
        // top为 res 的下标
        int top = -1;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 当 top > 0,即栈中有字符时,当前字符如果和栈中字符相等,弹出栈顶字符,同时 top--
            if (top >= 0 && res.charAt(top) == c) {
                res.deleteCharAt(top);
                top--;
            // 否则,将该字符 入栈,同时top++
            } else {
                res.append(c);
                top++;
            }
        }
        return res.toString();//将 StringBuffer(或者换成 StringBuilder)类型的 res 转换为普通的字符串类型(String)。这是因为 res 是一个可变的字符串缓冲区,而有些情况下可能需要返回一个不可变的字符串。
    }
}

方法二:常规栈操作,遍历字符串,将结果存入栈中,最后将栈中元素转化为可变字符串,再输出定长字符串

class Solution {
    public  String removeDuplicates(String s) {
        Stack<Character> stack = new Stack<>();

        for (char c : s.toCharArray()) {
            if (!stack.isEmpty() && stack.peek() == c) {
                stack.pop(); // 如果栈不为空且栈顶元素与当前字符相同,则弹出栈顶元素
            } else {
                stack.push(c); // 否则将当前字符压入栈中
            }
        }

        StringBuilder result = new StringBuilder();
        while (!stack.isEmpty()) {
            result.insert(0, stack.pop()); // 从栈底开始构建最终字符串
        }

        return result.toString();
    }
}

逆波兰表达式150

根据 逆波兰表示法,求表达式的值。其实就是后缀表达式,我们日常使用的是中缀表达式

有效的运算符包括 + , - , * , / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:

整数除法只保留整数部分。 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

示例 1:

  • 输入: [“2”, “1”, “+”, “3”, " * "]
  • 输出: 9
  • 解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

image-20240216112804550

知识拓展:这样的一个计算,使用中缀表达式,计算机计算起来会很复杂(因为,*,/,括号的存在),但是使用后缀表达式就很简单,直接从左到右计算,使用二叉树可以很直观的表现出计算的过程(左,右,中),而栈也可以很轻松计算。

即,当识别到符号时吗,根据符号的定义,从栈中出栈两个元素进行操作,并将结果再存入栈中;

java:

关键部分代码写法:

  for (String s : tokens) {
            if ("+".equals(s)) {        // leetcode 内置jdk的问题,不能使用==判断字符串是否相等
                stack.push(stack.pop() + stack.pop());      // 注意 - 和/ 需要特殊处理,stack会自动识别并弹出已经出现过的数,所以stack.pop()+stack.pop()其实就是将栈最外层两个数相加,前边的数是外侧第一个数,后边的是外侧第二个数;按照这个原理依次写逻辑
            } else if ("-".equals(s)) {
                stack.push(-stack.pop() + stack.pop());
            } else if ("*".equals(s)) {
                stack.push(stack.pop() * stack.pop());
            } else if ("/".equals(s)) {
                int temp1 = stack.pop();
                int temp2 = stack.pop();
                stack.push(temp2 / temp1);
            }
      		  else {
                stack.push(Integer.valueOf(s));//数字的情况,将字符串转换成数字加入到栈中;
            }

代码:

class Solution {
    public int evalRPN(String[] tokens) {
        Deque<Integer> stack = new LinkedList();
        for (String s : tokens) {
            if ("+".equals(s)) {        // leetcode 内置jdk的问题,不能使用==判断字符串是否相等
                stack.push(stack.pop() + stack.pop());      // 注意 - 和/ 需要特殊处理
            } else if ("-".equals(s)) {
                stack.push(-stack.pop() + stack.pop());
            } else if ("*".equals(s)) {
                stack.push(stack.pop() * stack.pop());
            } else if ("/".equals(s)) {
                int temp1 = stack.pop();
                int temp2 = stack.pop();
                stack.push(temp2 / temp1);
            } else {
                stack.push(Integer.valueOf(s));//数字的情况,将字符串转换成数字加入到栈中;
            }
        }
        return stack.pop();
    }
}

滑动窗口最大值239

单调队列类型题目

image-20240216145113761

如果无法理解单调队列可以看下边的解释:

摘自立扣评论:

单调队列真是一种让人感到五味杂陈的数据结构,它的维护过程更是如此.....就拿此题来说,队头最大,往队尾方向单调......有机会站在队头的老大永远心狠手辣,当它从队尾杀进去的时候,如果它发现这里面没一个够自己打的,它会毫无人性地屠城,把原先队里的人头全部丢出去,转身建立起自己的政权,野心勃勃地准备开创一个新的王朝.....这时候,它的人格竟发生了一百八十度大反转,它变成了一位胸怀宽广的慈父!它热情地请那些新来的“小个子”们入住自己的王国......然而,这些小个子似乎天性都是一样的——嫉妒心强,倘若见到比自己还小的居然更早入住王国,它们会心狠手辣地找一个夜晚把它们通通干掉,好让自己享受更大的“蛋糕”;当然,遇到比自己强大的,它们也没辙,乖乖夹起尾巴做人。像这样的暗杀事件每天都在上演,虽然王国里日益笼罩上白色恐怖,但是好在没有后来者强大到足以干翻国王,江山还算能稳住。直到有一天,闯进来了一位真正厉害的角色,就像当年打江山的国王一样,手段狠辣,野心膨胀,于是又是大屠城......历史总是轮回的。

太对了,对于单调队列的解释一目了然

这道题目解法:

牢记从头到尾是单调减小,最大数都存到头结点,新加入的数要和头结点比较大小,大的话直接清空队列,小的话,放在头结点后边,同时还要一直维护一个滑动窗口,头结点值划到最左端时,要去掉头结点,即最大值。

//利用双端队列手动实现单调队列
/**
 * 用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
 * 单调队列类似 (tail -->) 3 --> 2 --> 1 --> 0 (--> head) (右边为头结点,元素存的是下标)
 */
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        ArrayDeque<Integer> deque = new ArrayDeque<>();
        int n = nums.length;
        int[] res = new int[n - k + 1];
        int idx = 0;
        for(int i = 0; i < n; i++) {
            // 根据题意,i为nums下标,是要在[i - k + 1, i] 中选到最大值,只需要保证两点
            // 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
            while(!deque.isEmpty() && deque.peek() < i - k + 1){
                deque.poll();
            }
            // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
            while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }

            deque.offer(i);

            // 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
            // 将窗口内的最大值加入结果数组;
            if(i >= k - 1){
                res[idx++] = nums[deque.peek()];
            }
        }
        return res;
    }
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值