leetcode栈与队列刷题总结

本文介绍了数据结构中的栈、队列、堆(包括优先级队列)的概念和实现,以及它们在解决编程问题中的应用,如有效的括号匹配、逆波兰表达式和找高频元素等。同时,文章还讨论了如何用栈和队列相互转换来实现特定功能,并给出了相关面试题的解决方案。

一、理论

1. 栈

后进先出

//利用双端队列实现
Deque<> stack = new LinkedList<>();
//双端队列实现栈的常用方法
stack.push()
stack.pop()
stack.peek()
stack.isEmpty()

2. 队列

先进先出

Queue<> que = new LinkedList<>();
//个人习惯采用第一种
//成功返回true,失败时返回一个特殊值(取决于操作,为NULL或false)
que.offer();
que.poll();
que.peek();
//成功返回true,在操作失败时抛出异常
add(E e):添加一个元素到队尾
remove():获取队首的元素,并从队列中移除
element():获取队首的元素,但不从队列中移除
//
que.isEmpty();

3. 堆(优先级队列)

对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
从队头到队尾按从大到小排就是最大堆(大顶堆)—>队头元素相当于堆的根节点

4. 单调栈

通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
单调栈刷题总结

二、堆(优先队列)

1. 语法

  • 定义
    对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
    从队头到队尾按从大到小排就是最大堆(大顶堆)—>队头元素相当于堆的根节点
  • 比较器
    PriorityQueue 默认是小根堆,大根堆需要重写比较器(一定要记住相关语法,哪个是大哪个小顶堆)。
    可以在 new PriorityQueue<>() 中的参数部分加入比较器。
    具体写法是:(v1, v2) -> v2 - v1。
    Queue 类的输入是 offer() 方法/add,弹出是 poll() 方法。

T347. 前k个高频元素 (大顶堆) **

在这里插入图片描述

  • 思路分析
    建立个大顶堆,频率次数高的元素在队头,优先出来(有一些题解强调要小顶堆 不理解!)
    小技巧:如何遍历一个map集合
    getOrDefault方法
  • 代码实现
class Solution {
    //优先队列实现大顶堆
    //getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。
    public int[] topKFrequent(int[] nums, int k) {
        //1.计算值与出现频率
        Map<Integer,Integer> map = new HashMap<>();
        for(int i=0;i<nums.length;i++){
            map.put(nums[i],map.getOrDefault(nums[i],0)+1);
        }
        //2.创建大顶堆(从大到小),然后把map的值放到里面
        //大顶堆  [ [key,count], ... ]
        PriorityQueue<int[]> pq = new PriorityQueue<>( (x,y) -> y[1]-x[1]);
        //用map.entrySet 把map解析为 entry类型
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){
            pq.add(new int[]{entry.getKey(),entry.getValue()});
        }
        //3.存放结果
        int[] res = new int[k];
        for(int i = 0;i<k;i++){
            res[i] = pq.poll()[0];
        }
        return res;
    }
}

面试题 17.14 最小K个数 (小顶堆) *

在这里插入图片描述

class Solution {
    //小顶堆
    public int[] smallestK(int[] arr, int k) {
        PriorityQueue<Integer> pq = new PriorityQueue<Integer>((x,y)->(x-y));//小顶堆
        for(int i=0;i<arr.length;i++){
            pq.offer(arr[i]);//队头到队尾:从小到大
        }
        int[] res = new int[k];
        for(int i=0;i<k;i++){
            res[i] = pq.poll();
        }
        return res;
    }
}

面试题 17.09. 第 k 个数

T692. 前k个高频单词

三、栈与队列相互转换

T232. 用栈实现队列

在这里插入图片描述

class MyQueue {
    Deque<Integer> stackIn;
    Deque<Integer> stackOut;
    public MyQueue() {
        stackIn = new LinkedList<Integer>();
        stackOut = new LinkedList<Integer>();
    }
    
    public void push(int x) {
        stackIn.push(x);
    }
    
    public int pop() {
        contrustOut();
        return stackOut.pop();
    }
    
    public int peek() {
        contrustOut();
        return stackOut.peek();
    }
    
    public boolean empty() {
        return stackIn.isEmpty() && stackOut.isEmpty();
    }
    public void contrustOut(){
        if(!stackOut.isEmpty()) return;
        while(!stackIn.isEmpty()){
            stackOut.push(stackIn.pop());
        }
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

T225. 队列实现栈

在这里插入图片描述
利用辅助队列来实现

class MyStack {
    //queue内置方法
    //offer添加元素
    //poll返回第一个元素,并删除
    //peek返回第一个元素
//核心做法:构造满足栈要求的队列,利用一个辅助队列来对元素排序后,放到目标队列里面去
//画图分析思路较为清晰
    Queue<Integer> que1;//用来模拟栈
    Queue<Integer> que2;//辅助队列
    public MyStack() {
        que1 = new LinkedList<>();
        que2 = new LinkedList<>();
    }
    
    public void push(int x) {
        que2.offer(x);
        while (!que1.isEmpty()){
            que2.offer(que1.poll());//新的队列,符合栈要求的队列
        }
        Queue<Integer> tempQue = que1;
        que1 = que2;//队列1为最终结果
        que2 = tempQue;//队列2为空队列
    }
    
    public int pop() {
        return que1.poll();
    }
    
    public int top() {
        return que1.peek();
    }
    
    public boolean empty() {
        return que1.isEmpty();
    }
}

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

四、经典简单题

T20. 有效的括号(栈)

在这里插入图片描述

class Solution {
    public boolean isValid(String s) {
        Deque<Character> stack = new LinkedList<>();
        if(s.length()==1) return false;
        for(int i=0;i<s.length();i++){
            char ch = s.charAt(i);
            if(ch == ')'){
                if(stack.isEmpty() || stack.pop()!='(') return false;
            }
            else if(ch == ']'){
                if(stack.isEmpty() || stack.pop()!='[') return false;
            }
            else if(ch == '}'){
                if(stack.isEmpty() || stack.pop()!='{') return false;
            }else{
                stack.push(ch);
            }
        }
        return stack.isEmpty();
    }
}

T1047. 删除字符串中的所有重复字符(栈)

在这里插入图片描述

class Solution {
    public String removeDuplicates(String s) {
        Deque<Character> stack = new LinkedList<Character>();
        stack.push(s.charAt(0));
        for(int i=1;i<s.length();i++){
            if(stack.isEmpty() || s.charAt(i) != stack.peek()){
                stack.push(s.charAt(i));
            }else{
                stack.pop();
            }
        }
        String str = "";
        while(!stack.isEmpty()){
            str = stack.pop() + str;
        }
        return str;
    }
}

T150. 逆波兰表达式 **

在这里插入图片描述

class Solution {
    //遇到数字就入栈
    //遇到符号就弹出两个数字 进行处理后压入栈
    //Integer.valueOf  将基本类型int转换为包装类型Integer,或者将String转换成Integer
    public int evalRPN(String[] tokens) {
        Deque<Integer> stack = new LinkedList<Integer>();
        for(int i = 0;i<tokens.length;i++){
            String s = tokens[i];
            if( s.equals("+") ){// leetcode 内置jdk的问题,不能使用==判断字符串是否相等
                stack.push(stack.pop() + stack.pop());
            }else if(s.equals("-")){
               stack.push(-stack.pop()+stack.pop());
            }else if (s.equals("*")){
                stack.push(stack.pop()*stack.pop());
            }else if (s.equals("/")){
                int temp1 = stack.pop();
                int temp2 = stack.pop();
                stack.push(temp2/temp1);
            }else{
                stack.push(Integer.valueOf(s));
            }
        }
        return stack.pop();
    }
}

五、较为复杂的模拟题

1. 计算器系列问题

T224 基本计算器(没有乘除和空格,有 + - 括号)

在这里插入图片描述

  • 分析:
    用一个栈,记录数字和符号 顺序为数字、符号
    sign表示符号的正负
    遇到左括号 入栈 右括号 出栈
    如何把字符串变成数字
  • 代码实现
class Solution {
    //只有 加减括号
    //加减可以单独算,就用一个sign可以完成
    //遇到括号才入栈,依次是数字、符号
    //右括号就出栈,算出最终结果
    public int calculate(String s) {
        Deque<Integer> stack = new LinkedList<>();
        int sign = 1;//1代表正号 -1代表负号
        int len = s.length();
        int num = 0;
        for(int i=0;i<len;i++){
            char c = s.charAt(i);
            if(Character.isDigit(c)){
                int cur = c - '0';
                while(i+1<len && Character.isDigit(s.charAt(i+1))){
                    cur = cur * 10 + s.charAt(i+1) - '0';
                    i++;
                }
                num = num + sign * cur;//只要没遇到符号和括号 就把结果算出来
            }else if(c == '+'){
                sign = 1;
            }else if(c == '-'){
                sign = -1;
            }else if(c == '('){
                stack.push(num);
                num = 0;
                stack.push(sign);
                sign = 1;//先放数字后放符号
            }else if(c == ')'){
                num = stack.pop() * num + stack.pop();
            }
        }
        return num;
    }
}

T227. 基本计算器(加减乘除和空格,无括号)

在这里插入图片描述

T772

<think>嗯,用户想了解单调的实现和应用场景。从引用资料来看,之前已经提供了相关说明,但用户可能希望更系统化的总结。用户可能是算法学习者,正在准备面试或,需要清晰且可直接应用的实例。注意到引用[1][2][3]都强调了单调的核心特点:保持内元素单调性,用于高效查找邻近更大/更小元素。引用[4][5]给出了经典目(柱状图最大矩形)的代码实现,这些可以拆解说明。实现部分要突出三点:单调性维护(递增/递减)、出操作的含义(当前元素触发计算)、存储索引而非值的原因(方便计算边界)。应用场景需要区分典型问类型:边界搜索、面积优化、跨度计算。用户可能特别关注“为什么用而不用其他结构”——需要解释后进先出特性“最近邻”问的适配性。代码示例选用C++和Python双版本,因为引用[4]是C++而用户历史问含Python代码。注意在解释部分变量时保持一致性(如stk数组的top指针)。性能要明确O(n)的原因,避免用户误解退化情况。用户没提语言偏好,但上次回复含Python代码,所以优先Python示例。应用场景补充“矩阵最大矩形”的转化思路(引用[4]标提到),这是单调的进阶用法,能展示该算法的扩展性。</think> 单调是一种特殊的数据结构,其核心在于**维护内元素的单调性**(单调递增或递减)。它能够高效解决一系列需查找元素“邻近边界”的问,以下从实现和应用场景两方面详细说明: --- ### **一、单调实现** #### **核心思路** 1. **遍历顺序**:通常从左到右遍历数组 2. **内维护**:保持底到顶的单调性(增/减) 3. **出条件**:当新元素破坏单调性时,弹出顶元素并计算相关解 #### **代码示例(Python)** ```python def monotonic_stack(nums): stack = [] # 存储数组索引(通常比存值更灵活) res = [-1] * len(nums) # 初始化结果数组(例如存右侧第一个更大元素) for i in range(len(nums)): # 当非空且当前元素 > 顶元素(递减则用 <) while stack and nums[i] > nums[stack[-1]]: idx = stack.pop() # 弹出顶索引 res[idx] = nums[i] # 当前元素即为顶元素的"右侧更大元素" stack.append(i) # 当前元素索引入 return res # 示例:输入 [3, 1, 4, 2] # 输出: [4, 4, -1, -1] 表示:3右侧第一个大的是4,1右侧第一个大的是4,4和2右侧无更大元素 ``` #### **关键点** - **时间复杂为 $O(n)$**:每个元素最多入、出一次 - **中存储索引**:便于计算距离、区间宽度等 - **单调性选择**: - **递减**:找右侧第一个更大元素 - **递增**:找右侧第一个更小元素(如柱状图最大矩形) --- ### **二、经典应用场景** #### **1. 边界查找问** - **下一个更大/小元素**(Next Greater Element) - 例:`[2,1,2,4,3]` → 下一个更大元素 `[4,2,4,-1,-1]` [^1][^5] - **左侧第一个更大/小元素**(类似反向遍历) #### **2. 面积优化问** - **柱状图中最大矩形**(LeetCode 84)[^4] - **核心代码片段**(递增计算宽度): ```python stack = [-1] # 哨兵节点 max_area = 0 for i in range(len(heights)): while stack[-1] != -1 and heights[stack[-1]] > heights[i]: h = heights[stack.pop()] w = i - stack[-1] - 1 # 宽度 = 当前索引 - 新顶索引 - 1 max_area = max(max_area, h * w) stack.append(i) ``` - **最大矩形(01矩阵)**(LeetCode 85)[^4] - 转化为逐行的柱状图问 #### **3. 跨度计算问** - **每日温度**(LeetCode 739)[^5] - 求等待更暖天数的间隔:`[73,74,75,71,69,76]` → `[1,1,4,2,1,0]` - 解法:递减记录温度下标,出时计算天数差 #### **4. 删除重复字母(字典序最小)** - **关键点**:维护递增,处理字符最后出现位置 [^2] --- ### **单调适用条件** - 问需找到**某一侧最近的边界元素**(更大/更小) - 计算过程需要**利用弹出元素的中间结果**(如面积、距离) - 数组遍历方向边界查找方向一致(左→右找右边界,右→左找左边界) > 注:实际应用中,80% 单调可归结为以上场景。理解“单调性维护”和“边界触发计算”两个核心即可快速识别解法[^3][^5]。 --- **
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值