单调栈和单调队列

178 阅读2分钟

参考:

  1. 单调栈结构解决三道算法题
  2. 单调队列结构解决滑动窗口问题

单调栈

类型问题描述完成
496. 下一个更大元素 I
503. 下一个更大元素 II
739. 每日温度
剑指 Offer II 038. 每日温度
402. 移掉 K 位数字
901. 股票价格跨度
155. 最小栈
918. 环形子数组的最大和
321. 拼接最大数

496. 下一个更大元素 I

class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        // 存放的是实际的值
        Stack<Integer> stack = new Stack<Integer>();
        Map<Integer, Integer> map = new HashMap<>();
        int len2 = nums2.length;
        for (int i = 0; i < len2; i++) {
            map.put(nums2[i], i);
        }
        for (int j = len2 - 1; j >= 0; j--) {
            // 后面的元素都小于自己,因为是寻找的右侧的下一个更大元素,有j在这边挡着,j后面比j小的就没有用了
            while (!stack.isEmpty() && stack.peek() < nums2[j]) {
                stack.pop();
            }

            if (stack.isEmpty()) {
                // 把当前元素先放入栈里
                stack.push(nums2[j]);
                // 如果栈为空,说明对于j后面的元素不存在比它更大的元素
                nums2[j] = -1;
            } else {
                // 如果栈不为空,那此时的栈顶就是j的下一个更大元素
                int top = stack.peek();
                // 把当前元素先放入栈里
                stack.push(nums2[j]);
                nums2[j] = top;
            }
        }
        for (int i = 0; i < nums1.length; i++) {
            int idx2 = map.getOrDefault(nums1[i], -1);
            nums1[i] = idx2 == -1 ? -1 : nums2[idx2];
        }
        return nums1;
    }
}

503. 下一个更大元素 II

class Solution {
    public int[] nextGreaterElements(int[] nums) {
        Stack<Integer> stack = new Stack<Integer>();
        int len = nums.length;
        // 先把数组放到栈里
        for (int i = len - 1; i >= 0; i--) {
            while (!stack.isEmpty() && stack.peek() <= nums[i]) {
                stack.pop();
            }
            stack.push(nums[i]);
        }
        for (int i = len - 1; i >= 0; i--) {
            while (!stack.isEmpty() && stack.peek() <= nums[i]) {
                stack.pop();
            }
            if (stack.isEmpty()) {
                stack.push(nums[i]);
                nums[i] = -1;
            } else {
                int top = stack.peek();
                stack.push(nums[i]);
                nums[i] = top;
            }
        }
        return nums;
    }
}
// 还有一种循环数组取模的方法
class Solution {
    int[] nextGreaterElements(int[] nums) {
        int n = nums.length;
        int[] res = new int[n];
        Stack<Integer> s = new Stack<>();
        // 数组长度加倍模拟环形数组
        for (int i = 2 * n - 1; i >= 0; i--) {
            // 索引 i 要求模,其他的和模板一样
            while (!s.isEmpty() && s.peek() <= nums[i % n]) {
                s.pop();
            }
            res[i % n] = s.isEmpty() ? -1 : s.peek();
            s.push(nums[i % n]);
        }
        return res;
    }
}

739. 每日温度

相同问题:剑指 Offer II 038. 每日温度

class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        // 转化为求下一个更大元素的索引
        Stack<Integer> stack = new Stack<Integer>();
        int len = temperatures.length;
        int[] ans = new int[len];
        for(int i = len-1;i>=0;i--){
            while(!stack.isEmpty() && temperatures[stack.peek()] <= temperatures[i]){
                stack.pop();
            }
            ans[i] = stack.isEmpty() ? 0: stack.peek() - i;
            // 栈里存放元素的索引,方便计算天数
            stack.push(i);
        }
        return ans;
    }
}

402. 移掉 K 位数字

class Solution {
    public String removeKdigits(String num, int k) {
        int len = num.length();
        if(k == len){
            return "0";
        }
        char[] chars = num.toCharArray();
        Deque<Character> stack = new LinkedList<Character>();
        int remain = len - k;
        for(int i=0;i<len;i++){
            while(!stack.isEmpty() && k > 0 && stack.peekLast() > chars[i]){
                stack.pollLast();
                k--;
            }
            stack.offerLast(chars[i]);
        }
       
        // 如果是升序,那么无法剔除任何元素,此时k还是一开始的k,需要从头到尾取remain个字符返回
        while(k >0) {
            k--;
            stack.pollLast();
        }
        boolean isLeadZero = true;
        StringBuilder ans = new StringBuilder();
        while(!stack.isEmpty()){
            char cur = stack.pollFirst();
            if(isLeadZero && cur == '0'){
                continue;
            }
            // 出现了其他元素,零不是开头了
            isLeadZero = false;
            ans.append(cur);
        }
        return ans.length() == 0? "0":ans.toString();
    }
}

901. 股票价格跨度

class StockSpanner {
    Stack<StockInfo> stack;

    public StockSpanner() {
        stack = new Stack<>();
    }
    
    public int next(int price) {
        // 默认只有自身
        int ans = 1;
        while(!stack.isEmpty() && stack.peek().price <= price){
            StockInfo stockInfo = stack.pop();
            ans += stockInfo.count; 
        }
        // 把当前的价格放入栈中
        stack.push(new StockInfo(price, ans));
        return ans;
    }

    public class StockInfo{
        int price;
        int count;

        StockInfo(int price, int count){
            this.price = price;
            this.count = count;
        }
    }
}

155. 最小栈

正常思路。

class MinStack {
    Stack<Integer> stack1;
    Stack<Integer> stack2;

    public MinStack() {
        //维护两个栈,一个正常的栈,来保存元素的录入顺序,一个当前最小元素栈,来实现pop最小值
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }

    public void push(int val) {
        if (stack2.isEmpty() || val < stack2.peek()) {
            stack2.push(val);
        } else {
            stack2.push(stack2.peek());
        }
        stack1.push(val);
    }

    public void pop() {
        stack1.pop();
        stack2.pop();
    }

    public int top() {
        return stack1.peek();
    }

    public int getMin() {
        return stack2.peek();
    }
}

优化思路:不需要将两个栈的数量保持一致。

class MinStack {
    Stack<Integer> stack1;
    Stack<Integer> stack2;

    public MinStack() {
        //维护两个栈,一个正常的栈,来保存元素的录入顺序,一个当前最小元素栈,来实现pop最小值
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }

    public void push(int val) {
        // 这边注意一定要有等于,因为可能输入多个相同的元素
        if (stack2.isEmpty() || val <= stack2.peek()) {
            stack2.push(val);
        }
        stack1.push(val);
    }

    public void pop() {
        if (stack2.peek().equals(stack1.peek())) {
            stack2.pop();
        }
        stack1.pop();
    }

    public int top() {
        return stack1.peek();
    }

    public int getMin() {
        return stack2.peek();
    }
}

单调队列

既能够维护队列元素「先进先出」的时间顺序,又能够正确维护队列中所有元素的最值,这就是单调队列的作用。

类型问题描述完成
239. 滑动窗口最大值
1425. 带限制的子序列和
1696. 跳跃游戏 VI
862. 和至少为 K 的最短子数组
918. 环形子数组的最大和
剑指 Offer 59 - I. 滑动窗口的最大值
剑指 Offer 59 - II. 队列的最大值

239. 滑动窗口最大值

相同问题: 剑指 Offer 59 - I. 滑动窗口的最大值

//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        MonotonicQueue window = new MonotonicQueue();
        int len = nums.length;
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < len; i++) {
            // 先把窗口填满,需要留一个位置,因为size == k的时候就要计算第一次的最大值了
            if (i < k - 1) {
                window.push(nums[i]);
            } else {
                // 先加入队列
                window.push(nums[i]);
                // 记录最大值
                res.add(window.max());
                // 移除旧数字
                window.poll(nums[i - k + 1]);
            }
        }
        int[] arr = new int[res.size()];
        for (int i = 0; i < res.size(); i++) {
            arr[i] = res.get(i);
        }
        return arr;
    }

    public class MonotonicQueue {
        private LinkedList<Integer> maxq = new LinkedList<Integer>();

        // 给队尾插入元素
        public void push(int n) {
            // 把比自己小的元素都挤出去,保持队列的单调性
            while (!maxq.isEmpty() && maxq.getLast() < n) {
                maxq.pollLast();
            }
            maxq.addLast(n);
        }

        //剔除队头元素,如果相等的话
        public void poll(int n) {
            if (n == maxq.getFirst()) {
                maxq.pollFirst();
            }
        }

        // 返回当前窗口中的最大值
        public int max() {
            return maxq.getFirst();
        }
    }
}
//leetcode submit region end(Prohibit modification and deletion)

剑指 Offer 59 - II. 队列的最大值

class MaxQueue {
    LinkedList<Integer> monotonousQueue = null;
    LinkedList<Integer> elementQueue = null;

    public MaxQueue() {
        monotonousQueue = new LinkedList<>();
        elementQueue = new LinkedList<>();
    }

    public int max_value() {
        // 队头就是最大的元素
        return monotonousQueue.isEmpty() ? -1 : monotonousQueue.getFirst();
    }

    public void push_back(int value) {
        while (!monotonousQueue.isEmpty() && monotonousQueue.getLast() < value) {
            monotonousQueue.pollLast();
        }
        monotonousQueue.addLast(value);
        elementQueue.addLast(value);
    }

    public int pop_front() {
        if (elementQueue.isEmpty()) {
            return -1;
        }
        int frontVal = elementQueue.pollFirst();
        if (frontVal == monotonousQueue.getFirst()) {
            monotonousQueue.pollFirst();
        }
        return frontVal;
    }
}