美文网首页
算法-栈和队列算法总结

算法-栈和队列算法总结

作者: 攻城老狮 | 来源:发表于2021-11-24 10:13 被阅读0次

    栈和队列算法总结

    1 模拟

    1.1 使用栈实现队列

    // 232. 用栈实现队列
    // 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):实现 MyQueue 类:void push(int x) 将元素 x 推到队列的末尾 int pop() 从队列的开头移除并返回元素 int peek() 返回队列开头的元素 boolean empty() 如果队列为空,返回 true ;否则,返回 false
    class MyQueue {
    
        private Stack<Integer> stack1; //入栈
        private Stack<Integer> stack2; //出栈
    
        public MyQueue() {
            stack1 = new Stack<>();
            stack2 = new Stack<>();
        }
        
        public void push(int x) {
            stack1.push(x);
        }
        
        public int pop() {
            dumpStack();
            return stack2.pop();
        }
        
        public int peek() {
            dumpStack();
            return stack2.peek();
        }
        
        public boolean empty() {
            return stack1.isEmpty() && stack2.isEmpty();
        }
    
        private void dumpStack() {
            if (stack2.isEmpty()) {
                while (!stack1.isEmpty()) {
                    stack2.push(stack1.pop());
                }
            }
        }
       
    }
    

    1.2 使用队列实现栈

    // 225. 用队列实现栈
    // 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。实现 MyStack 类:void push(int x) 将元素 x 压入栈顶。int pop() 移除并返回栈顶元素。int top() 返回栈顶元素。boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
    class MyStack {
        private Queue<Integer> queue1;
        private Queue<Integer> queue2;
    
        public MyStack() {
            queue1 = new LinkedList<>();
            queue2 = new LinkedList<>();
        }
        
        public void push(int x) {
            queue2.offer(x);
            while (!queue1.isEmpty()) {
                queue2.offer(queue1.poll());
            }
            Queue<Integer> tmpQueue;
            tmpQueue = queue1;
            queue1 = queue2;
            queue2 = tmpQueue;
        }
        
        public int pop() {
            return queue1.poll();
        }
        
        public int top() {
            return queue1.peek();
        }
        
        public boolean empty() {
            return queue1.isEmpty();
        }
    }
    
    

    2 栈的应用

    2.1 栈操作

    // 20. 有效的括号
    // 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。有效字符串需满足:左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。
    class Solution {
        public boolean isValid(String s) {
            Stack<Character> stack = new Stack<>();
            for (char ch : s.toCharArray()) {
                if (ch == '(') {
                    stack.push(')');
                } else if (ch == '[') {
                    stack.push(']');
                } else if (ch == '{') {
                    stack.push('}');
                } else if (stack.isEmpty() || stack.peek() != ch) {
                    return false;
                } else {
                    stack.pop();
                }
            }
            return stack.isEmpty();
        }
    }
    
    // 1047. 删除字符串中的所有相邻重复项
    // 给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。在 S 上反复执行重复项删除操作,直到无法继续删除。在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
    class Solution {
        public String removeDuplicates(String s) {
            Stack<Character> stack = new Stack<>();
            for (char ch : s.toCharArray()) {
                if (stack.isEmpty() || stack.peek() != ch) {
                    stack.push(ch);
                } else {
                    stack.pop();
                }
            }
            StringBuilder sb = new StringBuilder();
            while (!stack.isEmpty()) {
                sb.append(stack.pop());
            }
            return sb.reverse().toString();
        }
    }
    
    // 150. 逆波兰表达式求值
    // 根据 逆波兰表示法,求表达式的值。有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
    class Solution {
        public int evalRPN(String[] tokens) {
            Stack<Integer> stack = new Stack<>();
            for (String token : tokens) {
                switch (token) {
                    case "+":
                    case "-":
                    case "*":
                    case "/":
                        int num1 = stack.pop();
                        int num2 = stack.pop();
                        stack.push(calc(num2,num1,token));
                        break;
                    default:
                        stack.push(Integer.parseInt(token));
                }
            }
            return stack.pop();
        }
    
        private int calc(int num1,int num2,String token) {
            switch (token) {
                case "+":
                    return num1 + num2;
                case "-":
                    return num1 - num2;
                case "*":
                    return num1 * num2;
                case "/":
                    return num1 / num2;
            }
            return 0;
        }
    }
    
    // 剑指 Offer II 037. 小行星碰撞
    // 给定一个整数数组 asteroids,表示在同一行的小行星。对于数组中的每一个元素,其绝对值表示小行星的大小,正负表示小行星的移动方向(正表示向右移动,负表示向左移动)。每一颗小行星以相同的速度移动。找出碰撞后剩下的所有小行星。碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞。
    class Solution {
        public int[] asteroidCollision(int[] asteroids) {
            Stack<Integer> stack = new Stack<>();
            for (int i = 0; i < asteroids.length; i++) {
                while (!stack.isEmpty() && stack.peek() > 0 && stack.peek() < -asteroids[i]) {
                    stack.pop();
                }
                if (!stack.isEmpty() && stack.peek() > 0 && stack.peek() == -asteroids[i]) {
                    stack.pop();
                } else if (stack.isEmpty() || asteroids[i] > 0 || stack.peek() < 0) {
                    stack.push(asteroids[i]);
                }
            }
            int[] res = new int[stack.size()];
            int index = 0;
            for (Integer item : stack) {
                res[index++] = item;
            }
            return res;
        }
    }
    
    // 剑指 Offer II 038. 每日温度
    // 请根据每日 气温 列表 temperatures ,重新生成一个列表,要求其对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。
    class Solution {
        public int[] dailyTemperatures(int[] temperatures) {
            int[] res = new int[temperatures.length];
            Stack<Integer> stack = new Stack<>();
            for (int i = 0; i < temperatures.length; i++) {
                while (!stack.isEmpty() && (temperatures[i] > temperatures[stack.peek()])) {
                    int pre = stack.pop();
                    res[pre] = i - pre;
                }
                stack.push(i);
            }
            return res;
        }
    }
    

    2.2 单调栈

    // 剑指 Offer II 039. 直方图最大矩形面积
    // 给定非负整数数组 heights ,数组中的数字用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
    class Solution {
        public int largestRectangleArea(int[] heights) {
            Stack<Integer> stack = new Stack<>();
            stack.push(-1);
            int maxArea = 0;
            for (int i = 0; i < heights.length; i++) {
                while (stack.peek() != - 1 && heights[stack.peek()] >= heights[i]) {
                    int height = heights[stack.pop()];
                    int width = i - stack.peek() - 1;
                    maxArea = Math.max(maxArea,height * width);
                }
                stack.push(i);
            }
            while (stack.peek() != -1) {
                int height = heights[stack.pop()];
                int width = heights.length - stack.peek() - 1;
                maxArea = Math.max(maxArea,height * width);
            }
            return maxArea;
        }
    }
    
    // 剑指 Offer II 040. 矩阵中最大的矩形
    // 给定一个由 0 和 1 组成的矩阵 matrix ,找出只包含 1 的最大矩形,并返回其面积。注意:此题 matrix 输入格式为一维 01 字符串数组。
    class Solution {
        public int maximalRectangle(String[] matrix) {
            if (matrix.length == 0 || matrix[0].length() == 0) return 0;
            int maxArea = 0;
            int[] heights = new int[matrix[0].length()];
            for (int i = 0; i < matrix.length; i++) {
                for (int j = 0; j < matrix[i].length(); j++) {
                    if (matrix[i].charAt(j) == '0') {
                        heights[j] = 0;
                    } else {
                        heights[j]++;
                    }
                }
                maxArea = Math.max(maxArea,largestRectangleArea(heights));
            }
            return maxArea;
        }
    
        private int largestRectangleArea(int[] heights) {
            Stack<Integer> stack = new Stack<>();
            stack.push(-1);
            int maxArea = 0;
            for (int i = 0; i < heights.length; i++) {
                while (stack.peek() != -1 && heights[stack.peek()] >= heights[i]) {
                    int height = heights[stack.pop()];
                    int width = i - stack.peek() - 1;
                    maxArea = Math.max(maxArea,height * width);
                }
                stack.push(i);
            }
            while (stack.peek() != -1) {
                int height = heights[stack.pop()];
                int width = heights.length - stack.peek() - 1;
                maxArea = Math.max(maxArea,height * width);
            }
            return maxArea;
        }
    }
    
    // 739. 每日温度
    // 请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。
    
    // 一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
    class Solution {
        public int[] dailyTemperatures(int[] temperatures) {
            int[] res = new int[temperatures.length];
            Stack<Integer> stack = new Stack<>();
            for (int i = 0; i < temperatures.length; i++) {
                while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
                    int preIndex = stack.pop();
                    res[preIndex] = i - preIndex;
                }
                stack.push(i);
            }
            return res;
        }
    }
    
    // 496. 下一个更大元素 I
    // nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。
    class Solution {
        public int[] nextGreaterElement(int[] nums1, int[] nums2) {
            int[] res = new int[nums1.length];
            Arrays.fill(res,-1);
            Map<Integer,Integer> map = new HashMap<>();
            for (int i = 0; i < nums1.length; i++) {
                map.put(nums1[i],i);
            }
            Stack<Integer> stack = new Stack<>();
            for (int i = 0; i < nums2.length; i++) {
                while (!stack.isEmpty() && nums2[i] > nums2[stack.peek()]) {
                    Integer preIndex = map.get(nums2[stack.peek()]);
                    if (preIndex != null) {
                        res[preIndex] = nums2[i];
                    }
                    stack.pop();
                }
                stack.push(i);
            }
            return res;
        }
    }
    
    // 503. 下一个更大元素 II
    // 给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
    
    class Solution {
        public int[] nextGreaterElements(int[] nums) {
            int[] res = new int[nums.length];
            Arrays.fill(res,-1);
            Stack<Integer> stack = new Stack<>();
            for (int i = 0; i < 2 * nums.length; i++) {
                while (!stack.isEmpty() && nums[i % nums.length] > nums[stack.peek()]) {
                    int preIndex = stack.pop();
                    res[preIndex] = nums[i % nums.length];
                }
                stack.push(i % nums.length);
            }
            return res;
        }
    }
    
    // 42. 接雨水
    // 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
    class Solution {
        public int trap(int[] height) {
            if (height == null || height.length <= 2) return 0;
            int sum = 0;
            Stack<Integer> stack = new Stack<>();
            stack.push(0);
            for (int i = 1; i < height.length; i++) {
                if (height[i] < height[stack.peek()]) {
                    stack.push(i);
                } else if (height[i] == height[stack.peek()]) {
                    stack.pop();
                    stack.push(i);
                } else {
                    while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
                        int midIndex = stack.pop();
                        if (!stack.isEmpty()) {
                            int h = Math.min(height[stack.peek()],height[i]) - height[midIndex];
                            int w = i - stack.peek() - 1;
                            sum += h * w;
                        }
                    }
                    stack.push(i);
                }
            }
            return sum;
        }
    }
    
    // 84. 柱状图中最大的矩形
    // 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
    class Solution {
        public int largestRectangleArea(int[] heights) {
            int res = 0;
            Stack<Integer> stack = new Stack<>();
            stack.push(0);
            // 数组扩容,在头和尾各加入一个元素
            int [] newHeights = new int[heights.length + 2];
            newHeights[0] = 0;
            newHeights[newHeights.length - 1] = 0;
            for (int index = 0; index < heights.length; index++){
                newHeights[index + 1] = heights[index];
            }
            heights = newHeights;
            for (int i = 1; i < heights.length; i++) {
                if (heights[i] > heights[stack.peek()]) {
                    stack.push(i);
                } else if (heights[i] == heights[stack.peek()]) {
                    stack.pop();
                    stack.push(i);
                } else {
                    while (heights[i] < heights[stack.peek()]) {
                        int midIndex = stack.pop();
                        int h = heights[midIndex];
                        int w = i - stack.peek() - 1;
                        res = Math.max(res,h * w);    
                    }
                    stack.push(i);
                }
            }
            return res;
        }
    }
    

    3 队列的应用

    3.1 队列操作

    // 剑指 Offer II 041. 滑动窗口的平均值
    // 给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算滑动窗口里所有数字的平均值。实现 MovingAverage 类:MovingAverage(int size) 用窗口大小 size 初始化对象。double next(int val) 成员函数 next 每次调用的时候都会往滑动窗口增加一个整数,请计算并返回数据流中最后 size 个值的移动平均值,即滑动窗口里所有数字的平均值。
    class MovingAverage {
        private Queue<Integer> queue;
        private int capacity;
        private int sum;
    
        /** Initialize your data structure here. */
        public MovingAverage(int size) {
            queue = new LinkedList<>();
            capacity = size;
        }
        
        public double next(int val) {
            queue.offer(val);
            sum += val;
            if (queue.size() > capacity) {
                sum -= queue.poll();
            }
            return (double) sum / queue.size();
        }
    }
    
    // 剑指 Offer II 042. 最近请求次数
    // 写一个 RecentCounter 类来计算特定时间范围内最近的请求。请实现 RecentCounter 类:RecentCounter() 初始化计数器,请求数为 0 。int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。保证 每次对 ping 的调用都使用比之前更大的 t 值。
    class RecentCounter {
        private Queue<Integer> queue;
        private int windowSize;
    
        public RecentCounter() {
            queue = new LinkedList<>();
            windowSize = 3000;
        }
        
        public int ping(int t) {
            queue.offer(t);
            while (t - queue.peek() > windowSize) {
                queue.poll();
            }
            return queue.size();
        }
    }
    

    3.2 二叉树广度遍历

    // 剑指 Offer II 043. 往完全二叉树添加节点
    // 完全二叉树是每一层(除最后一层外)都是完全填充(即,节点数达到最大,第 n 层有 2n-1 个节点)的,并且所有的节点都尽可能地集中在左侧。
    class CBTInserter {
        private Queue<TreeNode> queue;
        private TreeNode root;
    
        public CBTInserter(TreeNode root) {
            this.root = root;
            queue = new LinkedList<>();
            queue.offer(root);
            while (queue.peek().left != null && queue.peek().right != null) {
                TreeNode node = queue.poll();
                queue.offer(node.left);
                queue.offer(node.right);
            }
        }
        
        public int insert(int v) {
            TreeNode parent = queue.peek();
            TreeNode node = new TreeNode(v);
            if (parent.left == null) {
                parent.left = node;
            } else {
                parent.right = node;
                queue.poll();
                queue.offer(parent.left);
                queue.offer(parent.right);
            }
            return parent.val;
        }
        
        public TreeNode get_root() {
            return root;
        }
    }
    
    // 剑指 Offer II 044. 二叉树每层的最大值
    // 给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
    class Solution {
        public List<Integer> largestValues(TreeNode root) {
            List<Integer> res = new ArrayList<>();
            if (root == null) return res;
            Queue<TreeNode> queue = new LinkedList<>();
            queue.offer(root);
            while (!queue.isEmpty()) {
                int len = queue.size();
                int max = Integer.MIN_VALUE;
                for (int i = 0; i < len; i++) {
                    TreeNode node = queue.poll();
                    max = Math.max(max,node.val);
                    if (node.left != null) queue.offer(node.left);
                    if (node.right != null) queue.offer(node.right);
                }
                res.add(max);
            }
            return res;
        }
    }
    
    // 剑指 Offer II 045. 二叉树最底层最左边的值
    // 给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。假设二叉树中至少有一个节点。
    class Solution {
        public int findBottomLeftValue(TreeNode root) {
            int leftNum = root.val;
            Queue<TreeNode> queue = new LinkedList<>();
            queue.offer(root);
            while (!queue.isEmpty()) {
                int len = queue.size();
                leftNum = queue.peek().val;
                for (int i = 0; i < len; i++) {
                    TreeNode node = queue.poll();
                    if (node.left != null) queue.offer(node.left);
                    if (node.right != null) queue.offer(node.right);
                }
            }
            return leftNum;
        }
    }
    
    // 剑指 Offer II 046. 二叉树的右侧视图
    // 给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
    class Solution {
        public List<Integer> rightSideView(TreeNode root) {
            List<Integer> res = new ArrayList<>();
            if (root == null) return res;
            Queue<TreeNode> queue = new LinkedList<>();
            queue.offer(root);
            while (!queue.isEmpty()) {
                int len = queue.size();
                for (int i = 0; i < len; i++) {
                    TreeNode node = queue.poll();
                    if (node.left != null) queue.offer(node.left);
                    if (node.right != null) queue.offer(node.right);
                    if (i == len - 1) res.add(node.val);
                }
            }
            return res;
        }
    }
    

    3.2 优先级队列

    // 239. 滑动窗口最大值
    // 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
    class Solution {
        public int[] maxSlidingWindow(int[] nums, int k) {
            if (nums.length == 1) {
                return nums;
            }        
            int len = nums.length - k + 1;
            int[] res = new int[len];
            MyQueue queue = new MyQueue();
            for (int i = 0; i < k; i++) {
                queue.offer(nums[i]);
            }
            int index = 0;
            res[index++] = queue.peek();
            for (int i = k; i < nums.length; i++) {
                queue.poll(nums[i - k]);
                queue.offer(nums[i]);
                res[index++] = queue.peek();
            }
            return res;
        }
    
        private static class MyQueue {
            private Deque<Integer> queue = new LinkedList<>();
    
            public void poll(int val) {
                if (!queue.isEmpty() && queue.peek() == val) {
                    queue.poll();
                }
            }
    
            public void offer(int val) {
                while (!queue.isEmpty() && queue.getLast() < val) {
                    queue.removeLast();
                }
                queue.offer(val);
            }
    
            public int peek() {
                return queue.peek();
            }
        }
    }
    
    // 347. 前 K 个高频元素
    // 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
    class Solution {
        public int[] topKFrequent(int[] nums, int k) {
            int[] res = new int[k];
            Map<Integer,Integer> hash = new HashMap<>();
            for (int num : nums) {
                hash.put(num,hash.getOrDefault(num,0) + 1);
            }
            PriorityQueue<Map.Entry<Integer,Integer>> queue = new PriorityQueue<>((o1,o2)->o1.getValue() - o2.getValue());
            for (Map.Entry<Integer,Integer> entry : hash.entrySet()) {
                queue.offer(entry);
                if (queue.size() > k) {
                    queue.poll();
                }
            }
            for (int i = k - 1; i >=0; i--) {
                res[i] = queue.poll().getKey();
            }
            return res;
        }
    }
    

    相关文章

      网友评论

          本文标题:算法-栈和队列算法总结

          本文链接:https://www.haomeiwen.com/subject/iulntrtx.html