代码随想录【栈与队列篇】

本篇主要内容如下图大纲所示:
在这里插入图片描述

1.用栈实现队列

力扣题目链接

使用栈实现队列的下列操作:

push(x) – 将一个元素放入队列的尾部。

pop() – 从队列首部移除元素。

peek() – 返回队列首部的元素。

empty() – 返回队列是否为空。

示例:

MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false

思路: 用两个栈来模拟队列的操作

  • 入队没啥说的就是进栈 stackIn.push(), 比如 [1,2,3]

  • 出队就是返回栈底元素 1, 此时再借助一个栈 stackOut 把 stackIn的元素存进去就是 [3,2,1], 再返回stackOut.pop()就是 1 了。

  • 队列第一个元素 1 同理出队

  • 判空就是 两个栈同时为空的时候

代码如下:

public class MyQueue {
    
    Stack<Integer> stackIn;
    Stack<Integer> stackOut;
    
    public MyQueue(){
        stackIn = new Stack<>();
        stackOut = new Stack<>();
    }
    
    public void push(int x){
        stackIn.push(x);
    }
    public int pop(){
        dumpStackIn();
        return stackOut.pop();
    }
    public int peek(){
        dumpStackIn();
        return stackOut.peek();
    }

    public boolean isEmpty(){
        return stackIn.isEmpty() && stackOut.isEmpty();
    }

    public void dumpStackIn(){
        if(!stackOut.isEmpty()) return;
        while(!stackIn.isEmpty()){
            stackOut.push(stackIn.pop());
        }
    }
}

2.用队列实现栈

力扣题目链接

使用队列实现栈的下列操作:

push(x) – 元素 x 入栈

pop() – 移除栈顶元素

top() – 获取栈顶元素

empty() – 返回栈是否为空

注意:

  • 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
  • 你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
  • 你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。

思路:定义一个队列 ArrayDeque

  • 入栈就是入队,deque.addLast(x), [1,2,3]

  • 出栈就是把 3 移除,逻辑是 遍历该队列,把1添加到3后面,2又继续添加到1后面结束,队列变成 [3,2,1],

  • 此时再移除队列的首部元素3 deque.pollFirst();

  • 栈顶元素3获取 ,就是队列的 最后一个元素 deque.peekLast();

  • 判空就是队列是否为空

代码如下:

public class MyStack {
    // Deque 接口继承了 Queue 接口
    // 所以 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst
    //一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外) 重新添加到队列尾部,此时再去弹出元素就是栈的顺序了。
    ArrayDeque<Integer> deque;

    public MyStack() {
        deque = new ArrayDeque<Integer>();
    }

    public void push(int x) {
        deque.addLast(x);
    }

    public int pop() {
        int size = deque.size();
        size--;
        while (size-- > 0) {
            deque.addLast(deque.peekFirst());
            deque.pollFirst();
        }
        return deque.pollFirst();;
    }

    public int top() {
        return deque.peekLast();
    }
}

3.有效的括号

力扣题目链接

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

示例 1:

输入: “()”

输出: true

示例 2:

输入: “()[]{}”

输出: true

思路:

  • 用栈模拟括号匹配,左括号就把对应的右括号入栈,“({[ ]})” 即栈里元素为 )}] ,
  • 然后继续遍历拿到 右括号和栈里元素一一匹配,匹配成功则出栈(都匹配成功此时栈为空返回true),没匹配到则返回false(c != stack.peek())
  • 还有一种情况是 ) ,匹配过程中栈里为空,返回false

代码如下:

class Solution{
    public boolean isValid(String s){
        Deque<Character> stack = new LinkedList<>();
        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.peek()) {
                return false;
            }else {
                stack.pop();
            }
        }
        
        return stack.isEmpty();
    }
}

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

力扣题目链接

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:

输入:“abbaca”

输出:“ca”

解释:例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。

提示:

  • 1 <= S.length <= 20000
  • S 仅由小写英文字母组成。

思路:其实就是消消乐,跟括号匹配类似,定义一个栈,遍历字符串,相等就出栈,最后留下没匹配上的

代码如下:

class Solution{
    public String removeDuplicates(String s){
        //ArrayDeque会比LinkedList在除了删除元素这一点外会快一点
        //参考:https://stackoverflow.com/questions/6163166/why-is-arraydeque-better-than-linkedlist
        ArrayDeque<Character> stack = new ArrayDeque<>();
        for(char c : s.toCharArray()){
            if(stack.isEmpty() || stack.peek() != c){
                stack.push(c);
            }else{
                stack.pop();
            }
        }
        String str = "";
        //剩余的元素即为不重复的元素
        while (!stack.isEmpty()) {
            str = stack.pop() + str;
        }
        return str;
    }
}

5.逆波兰表达式求值

力扣题目链接

根据 逆波兰表示法,求表达式的值。

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

说明:

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

示例 1:

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

输入: ["4", "13", "5", "/", "+"]
输出: 6
解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:

输入: ["10", "6", "9", "3", "+", "-11", " * ", "/", " * ", "17", "+", "5", "+"]

输出: 22

解释:该算式转化为常见的中缀算术表达式为:

((10 * (6 / ((9 + 3) * -11))) + 17) + 5       
= ((10 * (6 / (12 * -11))) + 17) + 5       
= ((10 * (6 / -132)) + 17) + 5     
= ((10 * 0) + 17) + 5     
= (0 + 17) + 5    
= 17 + 5    
= 22    

逆波兰表达式:是一种后缀表达式,所谓后缀就是指运算符写在后面。

平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。

该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。

  • 适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。

思路:适合用栈操作运算:遇到数字则入栈;遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中。

class Solution{
    public int evalRPN(String[] tokens){
        ArrayDeque<Integer> stack = new ArrayDeque<>();
        for (String s : tokens) {
            if("+".equals(s)){
                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();
    }
}

6.滑动窗口最大值

力扣题目链接

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

进阶:

你能在线性时间复杂度内解决此题吗?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wqs9mn9K-1691504050521)(img.png)]

提示:

  • 1 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4
  • 1 <= k <= nums.length

思路:实现一个单调递减队列,每次滑动窗口维护队列的值,直接返回队首元素就是当前窗口的最大值

代码如下:

class MyQueue {
    Deque<Integer> deque = new LinkedList<>();

    void poll(int val){
        if(!deque.isEmpty() && val == deque.peek()){
            deque.poll();
        }
    }
    void add(int val){
       while (!deque.isEmpty() && val > deque.getLast()){
            deque.removeLast();
       }
       deque.add(val);
    }
    int peek(){
        return deque.peek();
    }
}
class Solution{
    public static int[] maxSlidingWindow(int[] nums, int k) {
        // nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
        if(nums.length == 1) return nums;
        int len = nums.length - k + 1;

        int[] res = new int[len];//存放结果的数组
        int num = 0;
        //自定义队列
        MyQueue1 myQueue = new MyQueue1();
        for(int i = 0; i < k; i++){
            myQueue.add(nums[i]); //3,-1添加进去了
        }
        res[num++] = myQueue.peek(); //3最大
        for(int i = k; i < nums.length; i++){ //第二次滑动从-3开始,3,-1,-3;第三次滑动5进来,3,-1,-3全部出队返回5就形成单调递减的队列
            myQueue.poll(nums[i - k]);
            myQueue.add(nums[i]);
            res[num++] = myQueue.peek();
        }
        return res;
    }
}

7.前 K 个高频元素

力扣题目链接

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

  • 输入: nums = [1,1,1,2,2,3], k = 2
  • 输出: [1,2]

示例 2:

  • 输入: nums = [1], k = 1
  • 输出: [1]

提示:

  • 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
  • 你的算法的时间复杂度必须优于 O ( n log ⁡ n ) O(n \log n) O(nlogn) , n 是数组的大小。
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
  • 你可以按任意顺序返回答案。

思路:用map存储元素和频次,用优先队列(建立小顶堆:队首到队尾从小到大排列的)取前k个高频次,小顶堆是从小到大排列的完全二叉树

遍历map,队列元素小于k时入队,大于k时,比较当前元素和根节点(也就是频次最小的节点)的大小,把小值出队

代码如下:

class Solution{
    public static int[] topKFrequent2(int[] nums, int k) {
        //[1,1,1,1,2,2,2,3,3,4,4,4] 3  返回1,2,4
        Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
        for(int num : nums){
            map.put(num, map.getOrDefault(num,0) + 1);
        }
        //1->4 2->3 3->2 4->3
        //在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
        //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
        //出现次数 2,3,4
        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2)->(pair1[1] - pair2[1]));

        for(Map.Entry<Integer,Integer> entry : map.entrySet()){ //小顶堆只需要维持k个元素有序
            if(pq.size() < k){ //小顶堆元素个数小于k个时直接加
                pq.add(new int[]{entry.getKey(), entry.getValue()});
            }else{
                //pq.peek()[1] : peek()队头元素的value值 2
                if(entry.getValue() > pq.peek()[1]){ //当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
                    pq.poll(); //弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了
                    pq.add(new int[]{entry.getKey(), entry.getValue()});
                }
            }
        }

        int[] ans = new int[k];
        for(int i = k - 1; i >= 0; i--){ //依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
            ans[i] = pq.poll()[0];
        }
        return ans;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值