【leetcode】栈与队列

参考自 代码随想录

总结

学会逆向思维

比如225. 用队列实现栈
入栈操作时,首先获得入栈前的元素个数 n,然后将元素入队到队列,再将队列中的前 n 个元素(即除了新入栈的元素之外的全部元素)依次出队并入队到队列,此时队列的前端的元素即为新入栈的元素,且队列的前端和后端分别对应栈顶和栈底。

比如20. 有效的括号
在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了。可以大幅度简化代码

简单

232. 用栈实现队列

参考:232. 用栈实现队列 - 力扣(LeetCode)

class MyQueue {

    Stack<Integer> s1,s2;

    public MyQueue() {
        s1=new Stack<>();// 负责进栈
        s2=new Stack<>();// 负责出栈
    }
    
    public void push(int x) {
        s1.push(x); // 进栈
    }

    // 从栈的栈顶移除并返回元素
    public int pop() {
        while(s2.isEmpty()){
            while(!s1.isEmpty()){
                // 如果s2为空则把s1的数据全部导入s2
                s2.push(s1.pop());
            }
        }
        return s2.pop();// 出栈
    }
    
    // 返回栈顶元素
    public int peek() {
        while(s2.isEmpty()){
            while(!s1.isEmpty()){
                s2.push(s1.pop());
            }
        }
        return s2.peek(); // 返回栈顶元素
    }
    
    public boolean empty() {
    	// 两个栈都空说明队列为空
        return s1.isEmpty()&&s2.isEmpty();
    }
}

225. 用队列实现栈

参考:225. 用队列实现栈 - 力扣(LeetCode)

方法一:使用两个队列实现一个后入先出(LIFO)的栈

  • 入 栈 时 存 入 q u e 1 入栈时存入que1 que1
  • 出 栈 时 把 q u e 1 中 的 元 素 ( 除 最 后 一 个 入 队 的 元 素 ) 导 入 q u e 2 出栈时把que1中的元素(除最后一个入队的元素)导入que2 que1()que2
    获 取 q u e 1 的 最 后 一 个 元 素 并 出 队 , 再 把 q u e 2 导 入 q u e 1 获取que1的最后一个元素并出队,再把que2导入que1 que1que2que1
  • 获 取 栈 顶 时 把 q u e 1 中 的 元 素 ( 除 最 后 一 个 入 队 的 元 素 ) 导 入 q u e 2 获取栈顶时把que1中的元素(除最后一个入队的元素)导入que2 que1()que2
    获取que1的最后一个元素并出队,再把这个元素导入que2,然后把que2导入que1
import java.util.*;

class MyStack {
    Queue<Integer> que1;
    Queue<Integer> que2;

    public MyStack() {
        que1 = new LinkedList<>();
        que2 = new LinkedList<>(); // 用来备份que1中的元素
    }

    public void push(int x) {
        que1.offer(x); // 入队 que1.add(x);也可以
    }

    public int pop() {
        // 返回que1的最后一个入队的元素

        int size = que1.size();
        while (size > 1) {
            // 把que1中的元素(除最后一个入队的元素)导入que2
            que2.offer(que1.poll());
            size--;
        }
        int result = que1.poll();// 要返回的值

        // 再把que2导入que1
        while (!que2.isEmpty()) {
            que1.offer(que2.poll());
        }

        return result;
    }

    public int top() {
        // 返回que1队尾元素

        int size = que1.size();
        while (size > 1) {
            // 把que1中的元素(除最后一个入队的元素)导入que2
            que2.offer(que1.poll());
            size--;
        }
        int result = que1.poll();// 要返回的值
        que2.offer(result); // 再把que1中最后一个入队的元素也导入que2

        // 再把que2导入que1
        while (!que2.isEmpty()) {
            que1.offer(que2.poll());
        }
        return result;
    }

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

    public static void main(String[] args) {
        MyStack myStack = new MyStack();
        myStack.push(1);
        myStack.push(2);
        System.out.println(myStack.top()); // 返回 2
        System.out.println(myStack.pop()); // 返回 2
        System.out.println(myStack.empty());// 返回 False
    }
}

方法二:使用两个队列实现一个后入先出(LIFO)的栈
参考:用队列实现栈 - 用队列实现栈 - 力扣(LeetCode)
在这里插入图片描述

class MyStack {
    Queue<Integer> queue1;
    Queue<Integer> queue2;

    /** Initialize your data structure here. */
    public MyStack() {
        queue1 = new LinkedList<Integer>();
        queue2 = new LinkedList<Integer>();
    }
    
    /** Push element x onto stack. */
    public void push(int x) {
        queue2.offer(x);
        while (!queue1.isEmpty()) {
            queue2.offer(queue1.poll());
        }
        Queue<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;
    }
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        return queue1.poll();
    }
    
    /** Get the top element. */
    public int top() {
        return queue1.peek();
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return queue1.isEmpty();
    }
}

方法三:使用一个队列实现一个后入先出(LIFO)的栈
和方法一类似

  • 入 栈 时 存 入 q u e u e 入栈时存入queue queue
  • 出 栈 时 把 q u e u e 中 的 元 素 依 次 出 队 ( 除 最 后 一 个 入 队 的 元 素 ) 并 重 新 添 加 到 q u e u e 末 尾 出栈时把queue中的元素依次出队(除最后一个入队的元素)并重新添加到queue末尾 queue()queue
    再 获 取 q u e u e 的 队 头 元 素 并 出 队 再获取queue的队头元素并出队 queue
  • 获 取 栈 顶 时 把 q u e u e 中 的 元 素 依 次 出 队 ( 除 最 后 一 个 入 队 的 元 素 ) 并 重 新 添 加 到 q u e u e 末 尾 获取栈顶时把queue中的元素依次出队(除最后一个入队的元素)并重新添加到queue末尾 queue()queue
    获取queue的队头并出队,再把这个元素导入queue末尾
class MyStack {
    Queue<Integer> queue;

    public MyStack() {
        queue=new LinkedList<>();
    }
    
    public void push(int x) {
        queue.offer(x);
    }
    
    public int pop() {
        // 返回que1的最后一个入队的元素

        int size=queue.size();        
        while(size>1){
            // 把queue中的元素(除最后一个入队的元素)导入queue末尾
            queue.offer(queue.poll()); 
            size--;
        }
        int result=queue.poll();// 要返回的值
        return result;
    }
    
    public int top() {
        // 返回queue队尾元素

        int size=queue.size();        
        while(size>1){
            // 把queue中的元素(除最后一个入队的元素)导入queue末尾
            queue.offer(queue.poll()); 
            size--;
        }
        int result = queue.poll();// 要返回的值
        queue.offer(result); // 再把queue中最后一个入队的元素也导入queue末尾
        return result;
    }
    
    public boolean empty() {
        return queue.isEmpty();
    }
}

方法四:使用一个队列实现一个后入先出(LIFO)的栈
参考:用队列实现栈 - 用队列实现栈 - 力扣(LeetCode)
在这里插入图片描述

class MyStack {
    Queue<Integer> queue;

    /** Initialize your data structure here. */
    public MyStack() {
        queue = new LinkedList<Integer>();
    }
    
    /** Push element x onto stack. */
    public void push(int x) {
        int n = queue.size();
        queue.offer(x);
        for (int i = 0; i < n; i++) {
            queue.offer(queue.poll());
        }
    }
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        return queue.poll();
    }
    
    /** Get the top element. */
    public int top() {
        return queue.peek();
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return queue.isEmpty();
    }
}

20. 有效的括号

参考:20. 有效的括号 - 力扣(LeetCode)

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(' || c == '[' || c == '{') {
                stack.push(c);//入栈
            } else {
                if (stack.isEmpty()) 
                	return false;//栈中无元素来匹配括号
                else {
                    Character p = stack.pop();//出栈来匹配括号
                    if (p=='(') {
                        if (c != ')') return false;
                    } else if (p=='[') {
                        if (c != ']') return false;
                    } else if (p=='{') {
                        if (c != '}') return false;
                    }
                }
	      	}
        if (stack.isEmpty())
            return true;//栈中为空表示匹配成功
        else
            return false;
    }
}

技巧:在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') {
                stack.push(')');//入栈右括号
            }else if (c == '[') {
                stack.push(']');
            }else if (c == '{') {
                stack.push('}');
            }else if(stack.isEmpty() || stack.peek()!=c){
                return false;
            }else{
                stack.pop(); // 出栈
            }
        }
        return stack.isEmpty();
    }
}

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

【参考:1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)

可以把字符串顺序放到一个栈中,然后如果相同的话 栈就弹出,这样最后栈里剩下的元素都是相邻不相同的元素了。

class Solution {
    public String removeDuplicates(String s) {
        if (s.length() <= 1)
            return s;

        Stack<Character> stack = new Stack<>();
        stack.push(s.charAt(0));
        for (int i = 1; i < s.length(); i++) {
            if (!stack.isEmpty() && s.charAt(i) == stack.peek()) {
                stack.pop();
            } else {
                stack.push(s.charAt(i));
            }
        }
        StringBuilder sb = new StringBuilder();
        while (!stack.isEmpty()) {
            sb.append(stack.pop());
        }
        return sb.reverse().toString();
    }
}

滑动窗口(双指针实现)

class Solution {
    public String removeDuplicates(String s) {
        if (s.length() <= 1)
            return s;

        char[] arr = s.toCharArray();

        int left = 0, right = 1;
        int len = arr.length;
        while (right < len) {
            if (arr[left] == arr[right]) {
                // 前移两位
                for (int i = left; i < len - 2; i++) {
                    arr[i] = arr[i + 2];
                }
                len -= 2;
                if (left >= 1) {
                    left--;
                    right--;
                }
            } else {
                left++;
                right++;
            }
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < len; i++) {
            sb.append(arr[i]);
        }
        return sb.toString();
    }
}

中等

150. 逆波兰表达式求值

参考:150. 逆波兰表达式求值 - 力扣(LeetCode)

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<String> stack = new Stack<>();
        for (int i = 0; i < tokens.length; i++) {
        	// 操作符
            if (tokens[i].equals("+") || tokens[i].equals("-")
                    || tokens[i].equals("*") || tokens[i].equals("/")) {

                int num1 = Integer.valueOf(stack.pop());
                int num2 = Integer.valueOf(stack.pop());
                
                if (tokens[i].equals("+"))
                    stack.push(String.valueOf(num2 + num1));
                else if (tokens[i].equals("-"))
                    stack.push(String.valueOf(num2 - num1));
                else if (tokens[i].equals("*"))
                    stack.push(String.valueOf(num2 * num1));
                else if (tokens[i].equals("/"))
                    stack.push(String.valueOf(num2 / num1));
            } else {
                stack.push(tokens[i]); // 数字
            }
        }
        return Integer.valueOf(stack.peek());
    }
}

347. 前 K 个高频元素 ***

参考:347. 前 K 个高频元素 - 力扣(LeetCode)

优先队列 PriorityQueue

从队头取元素,从队尾添加元素,再无其他取元素的方式。

参考:Java优先队列(PriorityQueue)_try to do-CSDN博客_java优先队列

PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。(不指定Comparator时默认为小顶堆


参考:347. 前 K 个高频元素 - 前 K 个高频元素 - 力扣(LeetCode)

  1. 借助 哈希表 来建立数字和其出现次数的映射,遍历一遍数组统计元素的频率
  2. 维护一个元素数目为 k 的最小堆
  3. 每次都将新的元素与堆顶元素(堆中频率最小的元素)进行比较,如果新的元素的频率比堆顶端的元素大,则弹出堆顶端的元素,将新的元素添加进堆中
  4. 最终,堆中的 k 个元素即为前 k 个高频元素

leetcode有bug,编译不通过,但本地执行正常

class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        // 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int item : nums) {
            if (map.containsKey(item)) {
                map.put(item, map.get(item) + 1);
            } else {
                map.put(item, 1);
            }
        }
        // 遍历map,用小顶堆保存频率最大的k个元素
        PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return map.get(o1) - map.get(o2);
            }
        });
        
        for (Integer key : map.keySet()) {
            if (pq.size() < k) {
                pq.add(key);
            } else if (map.get(key) > map.get(pq.peek())) {
                pq.poll();
                pq.add(map.get(key));
            }
        }
        // 取出最小堆中的元素
        ArrayList<Integer> list = new ArrayList<>();
        while (!pq.isEmpty()) {
            list.add(pq.poll());
        }
        Collections.reverse(list);// 反转
        return list;
    }
}

1823. 找出游戏的获胜者

【参考:1823. 找出游戏的获胜者 - 力扣(LeetCode)

双端队列 Deque

本题需要用循环队列实现,但java没有循环队列的api,所以使用双端队列 Deque来模拟

// 把队首前k-1个元素依次出队加入到队尾

头       尾
1 2 3 4 5


1 2 3依次出队并加入队尾
    4 5 1 2 3
    头      尾
class Solution {
    public int findTheWinner(int n, int k) {
        Deque<Integer> queue = new ArrayDeque<>();

        for(int i=1;i<=n;i++)
            queue.offerLast(i);

        while(queue.size()>1){
            for(int i=1;i<k;i++)
                queue.offerLast(queue.pollFirst()); // 把队首前k-1个元素依次出队加入到队尾

            queue.pollFirst();
        }

        return queue.getFirst();

    }
}

循环队列

push poll 那里要统一 先进再移 先出再移

class CircleQueue{
    int maxSize;
    int[] queue;
    int front;
    int rear;

    public CircleQueue(int size){
        maxSize = size+1; // 必须预留一个空间当做标识
        queue = new int[maxSize];
        front = 0; // front指向队头元素的当前位置
        rear = 0; // rear指向队尾元素的下一个位置
    }
    public boolean isEmpty(){
        return front == rear;
    }
    public boolean isFull(){
        return (rear +1 )% maxSize == front;
    }
    public int size(){
        return (rear - front + maxSize) % maxSize;
    }
    public boolean push(int num){
        if(isFull())
            throw new RuntimeException("队满,无法插入队");
        queue[rear]=num; // 先进再移
        rear=(rear+1)%maxSize;
        return true;
    }
    public int poll(){
        if(isEmpty()) throw new RuntimeException("队空,无法出队");
        int temp = queue[front]; // 先出再移
        front = (front+1)%maxSize;
        return temp;
    }
    public int peek(){
        if(isEmpty()) throw new RuntimeException("队空,无法出队");
        return queue[front];
    }
}

class Solution {    

    public int findTheWinner(int n, int k) {
        CircleQueue queue=new CircleQueue(n);

        for(int i=0;i<n;i++) 
            queue.push(i+1); // 编号从1开始

        while(queue.size()>1){

            for(int i=0;i<k-1;i++)
                queue.push(queue.poll()); // 把尾部前k-1个元素依次出队加入到头部

            queue.poll();
        }

        return queue.peek();
    }
}

困难

239. 滑动窗口最大值 ***

参考:239. 滑动窗口最大值 - 力扣(LeetCode)

参考:代码随想录# 239. 滑动窗口最大值

参考:单调队列结构解决滑动窗口问题 :: labuladong的算法小抄

「单调队列」队列中的元素全都是单调递增(或递减)的

// 暴力法
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int len=nums.length;
        int[] result=new int[len-k+1];
        
        for(int i=0;i<len-k+1;i++){
            int max=nums[i];
            for(int j=i+1;j<i+k;j++){
                max= nums[j]>max ? nums[j] : max;
            }
            result[i]=max;
        }
        return result;
    }
}

// 
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        MyQueue window = new MyQueue();
        int len = nums.length;
        int[] result = new int[len - k + 1];
        int index = -1;
        //先将前k的元素放入队列
        for (int i = 0; i < k; i++) {
            window.push(nums[i]);
        }
        result[++index] = window.max();

        for (int i = k; i < len; i++) {
            // 滑动窗口移除最前面的元素,队列判断是否要移除头部的元素
            window.poll(nums[i - k]);
            // 滑动窗口加入元素
            window.push(nums[i]);
            // 记录对应的最大值
            result[++index] = window.max();
        }
        return result;
    }

}
/* 单调队列的实现 */
class MyQueue {
    Deque<Integer> deque = new LinkedList<>();

	//弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
    //同时判断队列当前是否为空
    public void poll(int num) {
        if (!deque.isEmpty() && deque.getFirst() == num) {
            deque.pollFirst();
        }
    }

	//添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
    //保证队列元素单调递减
    //比如此时队列元素3,1,而2将要入队,比1大,所以1弹出,此时队列:3,2
    public void push(int num) {
    	// 将小于 num 的元素全部删除
        while (!deque.isEmpty() && deque.getLast() < num) {
            deque.pollLast();
        }
        // 然后将 num 加入尾部
        deque.addLast(num);
    }

	//队列队顶元素始终为最大值
    public int max() {
        return deque.getFirst(); // peek()也可以
    }
}

42. 接雨水

参考:42. 接雨水 - 力扣(LeetCode)

【参考:如何高效解决接雨水问题 :: labuladong的算法小抄

部分参考:《代码随想录》第129-136页

以下都按列方向计算

常规解法

class Solution {
    public int trap(int[] height) {
        int sum=0;
        int len=height.length;
        for(int i=0;i<len;i++){
        	// 第一根和最后一根柱子不接水
            if(i==0||i==len-1) continue;

            int rHeight=height[i];// 右边柱子的最高高度
            int lHeight=height[i];// 左边柱子的最高高度

            for(int r=i+1;r<len;r++){
                if(height[r]>rHeight)
                    rHeight=height[r];
            }
            for(int l=i-1;l>=0;l--){
                if(height[l]>lHeight)
                    lHeight=height[l];
            }
            // 计算该柱子所在列雨水的面积
            int h=Math.min(lHeight,rHeight)-height[i];// h>=0
            if(h>0)  sum+=h;
        }
        return sum;
    }
}

动态规划(备忘录)
maxLeft[i] 和 maxRight[i] 分别代表 height[0…i] 和 height[i…end] 的最高柱子高度

class Solution {
    public int trap(int[] height) {
        int sum=0;
        int len=height.length;
        int[] maxLeft=new int[len];
        int[] maxRight=new int[len];
        // 初始化
        maxLeft[0]=height[0];
        maxRight[len-1]=height[len-1];
        // 记录每个柱子的左边最大高度 从左向右计算
        for(int i=1;i<len;i++){
            maxLeft[i]=Math.max(height[i],maxLeft[i-1]);
        }
        // 记录每个柱子的右边最大高度 从右向左计算
        for(int i=len-2;i>=0;i--){
            maxRight[i]=Math.max(height[i],maxRight[i+1]);
        }
        for(int i=0;i<len;i++){
            int h=Math.min(maxLeft[i],maxRight[i])-height[i];
            if(h>0) sum+=h;
        }        
        return sum;
    }
}

双指针法
再把备忘录精简一下,用双指针边走边算
maxLeft是 height[0…left] 中最高柱子的高度,left 指针左边的最高柱子
maxRight是 height[right…end] 的最高柱子的高度 right指针右边的最高柱子

在这里插入图片描述

class Solution {
    public int trap(int[] height) {
        int sum=0;
        int left=0,right=height.length-1;
        int maxLeft=0, maxRight=0;
        while(left<right){
            maxLeft=Math.max(maxLeft,height[left]);
            maxRight=Math.max(maxRight,height[right]);

            if(maxLeft<maxRight){
                sum+=maxLeft-height[left];
                left++;
            }else{
                sum+=maxRight-height[right];
                right--;
            }
        }            
        return sum;
    }
}

单调栈法待定

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值