队列、栈专题


不要纠结,干就完事了,熟练度很重要!!!多练习,多总结!!!

LeetCode 20. 有效的括号

在这里插入图片描述

解题思路

遇到左括号就入栈,遇到右括号就去栈中寻找最近的左括号,看是否匹配,最后记得检查栈是否清空了,这才算匹配完成。

代码实现

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for(char c:s.toCharArray()){
            if(c == '(' || c == '{' || c == '['){
                stack.push(c);
            }else if(!stack.isEmpty() && leftOf(c) == stack.peek()){
                stack.pop();
            }else {
                return false;
            }
        }

        return stack.isEmpty();
    }

    public char leftOf(char c){
        if(c == ')'){
            return '(';
        }else if(c == ']'){
            return '[';
        }else if(c == '}'){
            return '{';
        }
        return ' ';
    }
}

LeetCode 921. 使括号有效的最少添加

在这里插入图片描述

解题思路

核心思路是以左括号为基准,通过维护对右括号的需求数need,来计算最小的插入次数。需要注意两个地方:

  1. 当need == -1的时候意味着什么?

因为只有遇到右括号)的时候才会need–,need == -1意味着右括号太多了,所以需要插入左括号。
比如说s = “))“这种情况,需要插入 2 个左括号,使得s变成”()()”,才是一个合法括号串。

  1. 算法为什么返回res + need?

因为res记录的左括号的插入次数,need记录了右括号的需求,当 for 循环结束后,若need不为 0,那么就意味着右括号还不够,需要插入。
比如说s = “))(“这种情况,插入 2 个左括号之后,还要再插入 1 个右括号,使得s变成”()()()”,才是一个合法括号串。

代码实现

class Solution {
    public int minAddToMakeValid(String s) {
        int res = 0, need = 0;
        for(char c: s.toCharArray()){
            if(c == '('){
                need++;
            }else if(c == ')'){
                need--;
                if(need == -1){
                    res++;
                    need = 0;
                }
            }
        }
        return res+need;
    }
}

LeetCode 1541. 平衡括号字符串的最少插入次数

在这里插入图片描述

解题思路

  1. 当need == -1时,意味着我们遇到一个多余的右括号,显然需要插入一个左括号。
if (s[i] == ')') {
    need--;
    // 说明右括号太多了
    if (need == -1) {
        // 需要插入一个左括号
        res++;
        // 同时,对右括号的需求变为 1
        need = 1;
    }
}
  1. 当遇到左括号时,若对右括号的需求量为奇数,需要插入 1 个右括号。因为一个左括号需要两个右括号嘛,右括号的需求必须是偶数。(记住res和need代表含义不同,一加一减不可抵消,在need==-1时还有额外判断逻辑)
if (s[i] == '(') {
    need += 2;
    if (need % 2 == 1) {
        // 插入一个右括号
        res++;
        // 对右括号的需求减一
        need--;
    }
}

代码实现

class Solution {
    public int minInsertions(String s) {
        int res = 0, need = 0;
        for(char c :s.toCharArray()){
            if(c == '('){
                need+=2;
                if (need % 2 == 1) {
                    res++;
                    need--;
                }
            }else if(c == ')'){
                need--;
                if(need == -1){
                    res++;
                    need = 1;
                }
            }
        }
        return res+need;
    }
}

LeetCode 232. 用栈实现队列

在这里插入图片描述

解题思路

将一个栈当作输入栈,用于压入 push 传入数据;另一个栈当作输出栈,用于 pop 和 peek 操作。
每次 pop 或 peek 时,若输出栈为空则将输入栈的全部数据依次弹出并压入输出栈,这样输出栈从栈顶往栈底的顺序就是队列从队首往队尾的顺序。

代码实现

class MyQueue {

    private Stack<Integer> s1, s2;

    public MyQueue() {
        s1 = new Stack<Integer>();
        s2 = new Stack<Integer>();
    }
    
    public void push(int x) {
        s1.push(x);
    }
    
    public int pop() {
        peek();
        return s2.pop();
    }
    
    public int peek() {
        if(s2.isEmpty()){
            while(!s1.isEmpty()){
                s2.push(s1.pop());
            }
        }
        return s2.peek();
    }
    
    public boolean empty() {
        return s1.isEmpty() && s2.isEmpty();
    }
}

LeetCode 155. 最小栈

在这里插入图片描述
在这里插入图片描述

解题思路

使得每个元素 a 与其相应的最小值 m 时刻保持一一对应。因此我们可以使用一个辅助栈,与元素栈同步插入与删除,用于存储与每个元素对应的最小值。

  1. 当一个元素要入栈时,我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中;
  2. 当一个元素要出栈时,我们把辅助栈的栈顶元素也一并弹出;
  3. 在任意一个时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。

代码实现

class MinStack {
    Stack<Integer> stack;
    Stack<Integer> minStack;
    /** initialize your data structure here. */
    public MinStack() {
        stack=new Stack<>();
        minStack=new Stack<>();
    }
    
    public void push(int x) {
        stack.push(x);
        if(minStack.isEmpty()){
            minStack.push(x);
        }else {
            int tmp=minStack.peek();
            if (tmp<x){
                minStack.push(tmp);
            }else {
                minStack.push(x);
            }
        }
    }
    
    public void pop() {
        if(!stack.isEmpty()&&!minStack.isEmpty()){
            stack.pop();
            minStack.pop();
        }
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}

LeetCode 71. 简化路径

在这里插入图片描述
在这里插入图片描述

解题思路

在这里插入图片描述

代码实现

class Solution {
    public String simplifyPath(String path) {
        String[] names = path.split("/");
        Deque<String> stack = new ArrayDeque<String>();
        for (String name : names) {
            if ("..".equals(name)) {
                if (!stack.isEmpty()) {
                    stack.pollLast();
                }
            } else if (name.length() > 0 && !".".equals(name)) {
                stack.offerLast(name);
            }
        }
        StringBuffer ans = new StringBuffer();
        if (stack.isEmpty()) {
            ans.append('/');
        } else {
            while (!stack.isEmpty()) {
                ans.append('/');
                ans.append(stack.pollFirst());
            }
        }
        return ans.toString();
    }
}

LeetCode 394. 字符串解码

在这里插入图片描述

解题思路

countStack存储重复次数,stringStack暂存上一次处理结果,currentString作为返回结果,从stringStack提取上次结果后结合最新一次操作即可获取最终答案。

代码实现

class Solution {
   public String decodeString(String s) {
   Deque<Integer> countStack = new ArrayDeque<>(); // 存储数字
   Deque<String> stringStack = new ArrayDeque<>(); // 存储字符串
   String currentString = ""; // 当前解码字符串
   int k = 0; // 当前的倍数

   for (char ch : s.toCharArray()) {
       if (Character.isDigit(ch)) {
           k = k * 10 + (ch - '0'); // 处理多位数
       } else if (ch == '[') {
           // 遇到 '[',将当前的字符串和数字推入各自的栈
           countStack.push(k);
           stringStack.push(currentString);
           currentString = ""; // 重置当前字符串
           k = 0; // 重置倍数
       } else if (ch == ']') {
           // 遇到 ']',解码
           StringBuilder temp = new StringBuilder(stringStack.pop());
           int repeatTimes = countStack.pop();
           for (int i = 0; i < repeatTimes; i++) {
               temp.append(currentString); // 重复当前字符串
           }
           currentString = temp.toString(); // 更新当前字符串
       } else {
           // 如果是字母,直接加到当前字符串
           currentString += ch;
       }
   	 }
   	 return currentString;
   }
}

单调栈

输入一个数组nums = [2,1,2,4,3],你返回数组[4,2,4,-1,-1]。因为第一个 2 后面比 2 大的数是 4; 1 后面比 1 大的数是 2;第二个 2 后面比 2 大的数是 4; 4 后面没有比 4 大的数,填 -1;3 后面没有比 3 大的数,填 -1。
在这里插入图片描述

int[] nextGreaterElement(int[] nums) {
    int n = nums.length;
    // 存放答案的数组
    int[] res = new int[n];
    Stack<Integer> s = new Stack<>(); 
    // 倒着往栈里放
    for (int i = n - 1; i >= 0; i--) {
        // 判定个子高矮
        while (!s.isEmpty() && s.peek() <= nums[i]) {
            // 矮个起开,反正也被挡着了。。。
            s.pop();
        }
        // nums[i] 身后的更大元素
        res[i] = s.isEmpty() ? -1 : s.peek();
        s.push(nums[i]);
    }
    return res;
}

LeetCode 496. 下一个更大元素 I

在这里插入图片描述

解题思路

题目说nums1是nums2的子集,那么我们先把nums2中每个元素的下一个更大元素算出来存到一个映射里,然后再让nums1中的元素去查表即可。

代码实现

class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        int[] greater = nextGreater(nums2);
        Map<Integer, Integer> map = new HashMap<>();
        for(int i = 0;i < greater.length;i++){
            map.put(nums2[i], greater[i]);
        }
        int[] res = new int[nums1.length];
        for(int i = 0;i < nums1.length;i++){
            res[i] = map.get(nums1[i]);
        }
        return res;
    }

    public int[] nextGreater(int[] nums){
        int n = nums.length;
        int[] res = new int[n];
        Stack<Integer> stack = new Stack<>();
        for(int i = n-1;i >= 0;i--){
            while(!stack.isEmpty() && stack.peek() < nums[i]){
                stack.pop();
            }
            res[i] = stack.isEmpty()? -1 : stack.peek();
            stack.push(nums[i]);
        }
        return res;
    }
}

LeetCode 739. 每日温度

在这里插入图片描述

解题思路

该题思路同《LeetCode 496. 下一个更大元素 I》不同点在于要求的是当前值与之后最大值的间隔。

代码实现

用Stack 还是Dequeue没区别,本质是为了保存一个遍历过程中的单调数据。
本题要求右侧的第一个最大元素,那么有两种遍历顺序:
1.倒序遍历,res数据也是倒序求值,另外注意res逻辑在while循环外

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        int[] res = new int[n];
        Stack<Integer> stack = new Stack<>();
        for(int i = n-1;i >=0 ;i--){
            while(!stack.isEmpty() && temperatures[stack.peek()] <= temperatures[i]){
                stack.pop();
            }
            res[i] = stack.isEmpty()? 0 : (stack.peek()-i);
            stack.push(i);
        }
        return res;
    }
}

2.正序遍历,res数据也是正序求值,另外注意res逻辑在while循环那

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        int[] res = new int[n];
        Stack<Integer> stack = new Stack<>();
        for(int i=0;i<n;i++){
           while(!stack.isEmpty()&&temperatures[i]>temperatures[stack.peek()]){
                res[stack.peek()]=i-stack.peek();
                stack.pop();
            }
            stack.push(i);
        }

        return res;
    }
}

LeetCode 503. 下一个更大元素 II

在这里插入图片描述

解题思路

题目要求环形数组,对于这种需求,常用套路就是将数组长度翻倍:
在这里插入图片描述

代码实现

class Solution {
    public int[] nextGreaterElements(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        Stack<Integer> stack = new Stack<>();
        for(int i = 2*n-1;i >= 0;i--){
            while(!stack.isEmpty() && stack.peek() <= nums[i%n]){
                stack.pop();
            }
            res[i%n] = stack.isEmpty() ? -1: stack.peek();
            stack.push(nums[i%n]);
        }
        return res;
    }
}

单调队列

在这里插入图片描述
如果每个元素被加入时都这样操作,最终单调队列中的元素大小就会保持一个单调递减的顺序。

/* 单调队列的实现 */
class MonotonicQueue {
    LinkedList<Integer> maxq = new LinkedList<>();
    public void push(int n) {
        // 将小于 n 的元素全部删除
        while (!maxq.isEmpty() && maxq.getLast() < n) {
            maxq.pollLast();
        }
        // 然后将 n 加入尾部
        maxq.addLast(n);
    }

    public int max() {
        return maxq.getFirst();
    }

    public void pop(int n) {
        if (n == maxq.getFirst()) {
            maxq.pollFirst();
        }
    }
}

为啥要发明「单调队列」这种结构呢,主要是为了解决下面这个场景:

给你一个数组window,已知其最值为A,如果给window中添加一个数B,那么比较一下A和B就可以立即算出新的最值;但如果要从window数组中减少一个数,就不能直接得到最值了,因为如果减少的这个数恰好是A,就需要遍历window中的所有元素重新寻找新的最值。

如果单纯地维护最值的话,优先级队列很专业,队头元素就是最值。但优先级队列无法满足标准队列结构「先进先出」的时间顺序,因为优先级队列底层利用二叉堆对元素进行动态排序,元素的出队顺序是元素的大小顺序,和入队的先后顺序完全没有关系。

与双指针的滑动窗口不同 ,每当窗口扩大(right++)和窗口缩小(left++)时,你单凭移出和移入窗口的元素即可决定是否更新答案。

但就本文开头说的那个判断一个窗口中最值的例子,你就无法单凭移出窗口的那个元素更新窗口的最值,除非重新遍历所有元素,但这样的话时间复杂度就上来了

LeetCode 239. 滑动窗口最大值

在这里插入图片描述

解题思路

在这里插入图片描述

代码实现

class Solution {

    public int[] maxSlidingWindow(int[] nums, int k) {
        List<Integer> list = new ArrayList<>();
        MaxQueue queue = new MaxQueue();
        for(int i = 0;i < nums.length;i++){
            if(i < k-1){
                queue.push(nums[i]);
            }else{
                queue.push(nums[i]);
                list.add(queue.max());
                queue.pop(nums[i-k+1]);
            }
        }

        int[] res = new int[list.size()];
        for(int i = 0; i < list.size();i++){
            res[i] = list.get(i);
        }
        return res;
    }
}

class MaxQueue{

    LinkedList<Integer> queue = new LinkedList<>();

    public void push(int n){
        while(!queue.isEmpty() && queue.getLast() < n){
            queue.pollLast();
        }
        queue.addLast(n);
    }

    public int max(){
        return queue.getFirst();
    }

    public void pop(int n){
        if(!queue.isEmpty() && queue.getFirst() == n){
            queue.pollFirst();
        }
    }
}

数组去重

LeetCode 316. 去除重复字母

在这里插入图片描述

解题思路

  • 要求一:要去重。
  • 要求二:去重字符串中的字符顺序不能打乱s中字符出现的相对顺序。
  • 要求三: 在所有符合上一条要求的去重字符串中,字典序最小的作为最终结果。

在stk.peek() > c时才会 pop 元素,其实这时候应该分两种情况:
情况一、如果stk.peek()这个字符之后还会出现,那么可以把它 pop 出去,反正后面还有嘛,后面再 push 到栈里,刚好符合字典序的要求。
情况二、如果stk.peek()这个字符之后不会出现了,前面也说了栈中不会存在重复的元素,那么就不能把它 pop 出去,否则你就永远失去了这个字符。

代码实现

单调栈:

class Solution {
    public String removeDuplicateLetters(String s) {
        Stack<Character> stack = new Stack<>();
        boolean[] flag = new boolean[256];
        int[] count = new int[256];
        for(char c : s.toCharArray()){
            count[c]++;
        }

        for(char c : s.toCharArray()){
            count[c]--;
            if(flag[c]){
                continue;
            }
            while(!stack.isEmpty() && stack.peek() > c){
                if(count[stack.peek()] == 0){
                    break;
                }
                flag[stack.pop()]=false;
            }
            stack.push(c);
            flag[c]=true;
        }
        StringBuilder sb = new StringBuilder();
        while(!stack.isEmpty()){
            sb.append(stack.pop());
        }
        return sb.reverse().toString();
    }
}

单调队列:

class Solution {
    public String removeDuplicateLetters(String s) {
        LinkedList<Character> queue = new LinkedList<>();
        boolean[] flag = new boolean[256];
        int[] count = new int[256];
        for(char c : s.toCharArray()){
            count[c]++;
        }

        for(char c : s.toCharArray()){
            count[c]--;
            if(flag[c]){
                continue;
            }
            while(!queue.isEmpty() && queue.getLast() > c){
                if(count[queue.getLast()] == 0){
                    break;
                }
                flag[queue.pollLast()]=false;
            }
            queue.addLast(c);
            flag[c]=true;
        }
        StringBuilder sb = new StringBuilder();
        while(!queue.isEmpty()){
            sb.append(queue.pollFirst());
        }
        return sb.toString();
    }
}

中位数

LeetCode 295. 数据流的中位数

在这里插入图片描述

解题思路

在这里插入图片描述
这个小倒三角形相当于一个从小到大的有序数组,这个梯形相当于一个从大到小的有序数组。
小倒三角不就是个大顶堆嘛,梯形不就是个小顶堆嘛,中位数可以通过它们的堆顶元素算出来。

假设元素总数是n,如果n是偶数,我们希望两个堆的元素个数是一样的,这样把两个堆的堆顶元素拿出来求个平均数就是中位数;如果n是奇数,那么我们希望两个堆的元素个数分别是n/2 + 1和n/2,这样元素多的那个堆的堆顶元素就是中位数。

代码实现

class MedianFinder {
    PriorityQueue<Integer> small;
    PriorityQueue<Integer> large;

    public MedianFinder() {
        small = new PriorityQueue<>((a, b) -> {
            return b-a;
        });
        large = new PriorityQueue<>();
    }
    
    public void addNum(int num) {
        if(small.size() >= large.size()){
            small.offer(num);
            large.offer(small.poll());
        }else{
            large.offer(num);
            small.offer(large.poll());
        }
    }
    
    public double findMedian() {
        if(large.size() > small.size()){
            return large.peek();
        }else if(large.size() < small.size()){
            return small.peek();
        }
        return (small.peek()+large.peek())/2.0;
    }
}

实现计算器

LeetCode 224. 基本计算器

在这里插入图片描述

LeetCode 227. 基本计算器 II

在这里插入图片描述

解题思路

在这里插入图片描述

  • 当i走到了算式的尽头(i == s.size() - 1),也应该将前面的数字入栈.
  • 乘除法优先于加减法体现在,乘除法可以和栈顶的数结合,而加减法只能把自己放入栈。
  • 括号包含的算式,我们直接视为一个数字就行了。递归的开始条件和结束条件是什么?遇到(开始递归,遇到)结束递归.

注:
每次循环走到符号判断分支!Character.isDigit( c )时,处理的是上一位的运算数字,即(3+5/2)中的3,因为在首次运算前,先将sign赋值为+,而sign的更新和num的重置是在switch的符号分支逻辑之后的进行的。这也是为什么最后一位数字(i == s.length() - 1)要特殊处理,sign符号已经上次更新过了,且遍历到字符串尾部,不会再有后续switch的逻辑了,所以这次就要进行计算。

代码实现

class Solution {
    public int calculate(String s) {
        return helper(s);
    }

    int i = 0;
    public int helper(String s){
        int num = 0;
        char sign = '+';
        Deque<Integer> stack = new LinkedList<>();
        for(;i < s.length();i++){
            char c = s.charAt(i);
            if(Character.isDigit(c)){
                num = 10*num+(c-'0');
            }else if(c == '('){
                i++;
                num = helper(s);
            }
            if((!Character.isDigit(c) && c != ' ') || i == s.length() - 1){
                switch(sign){
                    case('+'):
                        stack.addFirst(num);
                        break;
                    case('-'):
                        stack.addFirst(-num);
                        break;
                    case('*'):
                        stack.addFirst(stack.removeFirst()*num);
                        break;
                    case('/'):
                        stack.addFirst(stack.removeFirst()/num);
                        break;
                }
                sign = c;
                num = 0;
            }
            if(c == ')'){
                break;
            }

        }
        int sum = 0;
        while(!stack.isEmpty()){
            sum+=stack.removeFirst();
        }
        return sum;
    }
}

LeetCode 150. 逆波兰表达式求值

在这里插入图片描述

解题思路

逆波兰表达式严格遵循「从左到右」的运算。计算逆波兰表达式的值时,使用一个栈存储操作数,从左到右遍历逆波兰表达式,进行如下操作:

  • 如果遇到操作数,则将操作数入栈;
  • 如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈。

整个逆波兰表达式遍历完毕之后,栈内只有一个元素,该元素即为逆波兰表达式的值。

代码实现

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack=new Stack<>();
        for (String s:tokens){
            if (s.equals("+")){
                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 temp=stack.pop();
                stack.push(stack.pop()/temp);
            }else {
                stack.push(Integer.parseInt(s));
            }
        }
        return stack.pop();
        
    }
}

总结

本题来源于Leetcode中 归属于队列、栈类型题目。
同许多在算法道路上不断前行的人一样,不断练习,修炼自己!
如有博客中存在的疑问或者建议,可以在下方留言一起交流,感谢各位!

觉得本博客有用的客官,可以给个点赞+收藏哦! 嘿嘿

喜欢本系列博客的可以关注下,以后除了会继续更新面试手撕代码文章外,还会出其他系列的文章!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值