42. 接雨水(单调递减栈)

class Solution {
    public int trap(int[] height) {
        int res = 0;
        Stack<Integer> leftWallStack = new Stack();
        int len = height.length;
        leftWallStack.push(0);
        for (int i=1;i<len;i++) {
            int rightWallIndex = i;
            int stackTopIndex = leftWallStack.peek();
            // 栈顶元素要当底部,右边出现比栈顶元素高的,那么它就可以顺利当底部。可以出栈了
            while (!leftWallStack.isEmpty() && height[rightWallIndex] > height[stackTopIndex]) {
                 // 出栈
                leftWallStack.pop();
                if (leftWallStack.isEmpty()) {
                    break;
                }
                // 栈的下一个元素要当左边的墙
                int leftWallIndex = leftWallStack.peek();
                // 左边的墙也要比出栈的容器底部高,才可以接到雨水
                if (height[leftWallIndex] > height[stackTopIndex]) {
                    int h = Math.min(height[leftWallIndex], height[rightWallIndex]) - height[stackTopIndex];
                    int w = rightWallIndex - leftWallIndex - 1;
                    res = res + h*w;
                }
                stackTopIndex = leftWallIndex;
            }
            // 不管怎么样,用来比较的右边新元素每次都要入栈
            leftWallStack.push(i);
        }
        return res;
    }

 第二次解

class Solution {
    public int trap(int[] height) {
        int sum = 0;
        Stack<Integer> stack = new Stack();
        // 单调递减栈
        for (int i=0;i<height.length;i++) {
            // 栈为空,直接入栈
            if (stack.isEmpty()) {
                stack.push(i);
            }
            else {
                // 比栈顶高度小,直接入栈(因为是单调递减栈)
                if (height[i] < height[stack.peek()]) {
                    stack.push(i);
                }
                else {
                    // 比栈顶高度大就循环出栈接雨水。直到小了,再入栈
                    while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
                        // 栈顶必出
                        int top = stack.pop();
                        // 栈顶出来可能又为空,没法取栈顶下一个了,break
                        if (stack.isEmpty()) {
                            break;
                        }
                        // 栈顶的下一个元素
                        int topnext = stack.peek();
                        // 当前 height[i] 和栈顶的下一个元素充当左右墙,栈顶充当底部
                        int h = Math.min(height[i], height[topnext]) - height[top];
                        // 注意要多 -1
                        int w = i-topnext-1;
                        sum = sum + h*w;
                    }
                    // 循环到终于比栈顶大了,入栈
                    stack.push(i);
                }
            }
        }
        return sum;
    }
}

 

剑指 Offer 30. 包含min函数的栈

155. 最小栈

 

用一个辅助栈 minStack,记录当前最小的元素

push: 如果当前加入的元素比 minStack 栈顶部的小【或等于】,为什么要有等于看代码注释】,就把这个元素也 push 进入 minStack

pop: 从主栈弹出,如果主栈弹出的这个元素和 minStack 相等,说明这个最小的已经走了,minStack 也要 pop 弹出

top:主栈的栈顶(peek)

min: 辅助栈 minStack 的栈顶(peek)

class MinStack {

    private Stack<Integer> mainStack;

    private Stack<Integer> minStack;

    /** initialize your data structure here. */
    public MinStack() {
        mainStack = new Stack();
        minStack = new Stack();
    }
    
    public void push(int x) {
        mainStack.push(x);
         // minStack 放的是当前最小,因此只有新入的元素比 minStack 栈顶小,才入栈
        // 一定要注意这里要加 = 否则有重复最小元素,只入栈了一个的话,那 pop 的时候这个出栈,之后 minStack.peek() 就会报错 
        if (minStack.isEmpty() || x <= minStack.peek()) {
            minStack.push(x);
        }
    }
    
    public void pop() {
        int top = mainStack.pop();
        // 主栈出栈的时候,如果 minStack 栈顶与主栈栈顶相同,它也要出栈
        if (top == minStack.peek()) {
            minStack.pop();
        }
    }
    
    public int top() {
        return mainStack.peek();
    }
    
    public int min() {
        return minStack.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.min();
 */    

 

 

栈 Stack 是 push 与 pop

队列 Queue 是 offer 与 poll

题解:

https://leetcode.cn/problems/sliding-window-maximum/solution/dong-hua-yan-shi-dan-diao-dui-lie-239hua-hc5u/

我们期望:

   维持一个单调递减队列,队列内的元素都是当前窗口内的元素。队列首的元素是最大值,也是当前窗口的最大值

有两个问题:

(1)怎么来维持单调递减呢?也就是说窗口右移的时候,怎么把右边新加入窗口的元素加入到队列中去?

offer 操作:加入队末,前面有比它它小的,就移除,直到队列为空 或 遇到一个比它大的,才推进去

(2)怎么来保证队列中的元素都是窗口内的呢?也就是窗口右移的时候,怎么把左边新移出窗口的元素从队列中去除?

poll 操作:如果是普通的队列,队首的元素直接移除就好了。但是前面的 offer 操作比较特殊,可能窗口最左的元素在前面 offer 的时候因为小于队末新加元素已经被移除了,所以要将队首元素和窗口最左元素比较,如果相等,再将其移除。

这样 peek() 操作,直接获取队首元素,就是当前窗口的最大值。

注意:

  • 以上操作都只是操作队列首和队列尾,所以用 LinkedList 比较合适,多用 getFirst()  removeFirst() getLast() removeLast() 等操作,不要用下标操作
  • 刚开始左右指针 left right 下标都是 0,要左指针不动,右指针右移 k 步完成窗口初始化
  • 后面就是 while(right<nums.length)
                // 将窗口右边的新元素加入单调递减队列
                offer(nums[right++]);
                // 把窗口左边已经移出窗口的元素,也移出队列
                pollIfEquals(nums[left++]);
                // 单调递减队列,队首元素即为当前窗口的的最大值
                res[i++] = peek();
class Solution {

    LinkedList<Integer> queue;

    public Solution() {
        // 用链表类型的 List 来实现
        queue = new LinkedList();
    }

    public int[] maxSlidingWindow(int[] nums, int k) {
        // 最后结果的长度是 nums.length - k + 1
        int resLen = nums.length - k + 1;
        int[] res = new int[resLen];
        int left = 0;
        int right = 0;
        // 窗口初始化
        for (int i=0;i<k;i++) {
            offer(nums[right++]);
        }
        res[0] = peek();
        // 窗口右移
        int i=1;
        while(right<nums.length) {
            // 将窗口右边的新元素加入单调递减队列
            offer(nums[right++]);
            // 按理说应该把窗口左边已经移出窗口的元素,也移出队列
            // 但是可能把右边新元素推到队列的过程中,就已经把这个左边的元素移出去了,所以要比较如果相等,才移出,否则 doNothing
            pollIfEquals(nums[left++]);
            // 单调递减队列,队首元素即为当前窗口的的最大值
            res[i++] = peek();
        }
        return res;
    }

    // 将 num 推到队列中,使其维持单调递减
    private void offer(int num) {
        if (queue.isEmpty()) {
            queue.add(num);
            return;
        }
        // 前面有比它它小的,就移除,直到队列为空 或 遇到一个比它大的,才推进去
        while (queue.size() > 0 && queue.getLast() < num) {
            queue.removeLast();
        }
        queue.add(num);
    }

    // 如果队首元素是 n ,则弹出来。如果不是,do nothing
    private void pollIfEquals(int n) {
        if (peek() == n) {
            queue.removeFirst();
        }
    }

    // 获取队首,因为是单调递减队列,所以也是最大值
    private Integer peek() {
        return queue.getFirst();
    }

}

 

739. 每日温度

官方题解

https://leetcode.cn/problems/daily-temperatures/solution/mei-ri-wen-du-by-leetcode-solution/

[73,74,75,71,69,72,76,73]

用单调递减栈来实现:

  • 栈为空,或者 当前元素比栈顶小(<=)则进栈
  • while(stack不为空 && 当前元素比栈顶大)则出栈

这样,每个元素遇到的让它出栈的哪个元素 就是 它遇到的后面第一个比它温度高的那一天

而让它出栈的那个元素的下标 到 自己的距离就是要求的结果。所以栈里也要存下标。

Stack<List<Integer>> stack 的每个元素是一个 List,List[0] 存温度下标, List[1] 存温度值

res[stackTop.get(0)] = i - stackTop.get(0);
class Solution {
    public int[] dailyTemperatures(int[] temperatures) {
        // 单调递减栈, List[0] 存温度下标, List[1] 存温度值
        Stack<List<Integer>> stack = new Stack();
        int[] res = new int[temperatures.length];
        for (int i=0;i<temperatures.length;i++) {
            // 单调递减栈。栈为空 或 当前元素比栈顶小则进栈
            if (stack.isEmpty() || temperatures[i] <= stack.peek().get(1)) {
                stack.push(Arrays.asList(i, temperatures[i]));
                continue;
            }
            // 注意 while 条件要在【前面】加上一个 !stack.isEmpty() 防止栈已经空掉
            while(!stack.isEmpty() && temperatures[i] > stack.peek().get(1)) {
                List<Integer> stackTop = stack.pop();
                // 结果[弹出元素的下标] = 当前元素下标到弹出元素下标的距离
                res[stackTop.get(0)] = i - stackTop.get(0);
            }
            // 一定要注意这个,当前比栈顶大后,一直到遇到一个比它小的,那么就要把它推入栈里
            stack.push(Arrays.asList(i, temperatures[i]));
        }
        return res;
    }
}

 

84. 柱状图中最大的矩形

维持一个单调递增栈(存放下标)

初始化栈底放入一个 -1,后面每次判断栈是否空都是用  stack.peek() != -1

遍历 heigths 数组

每当heights数组新元素比栈顶大或等于,直接入栈

每当heights数组新元素比栈顶小

  • topIndex = stack.pop() 栈顶出栈,area = heights[topIndex] * (i-stack.peek()-1)。以此更新结果。
  • 直到heights数组新元素比栈顶大或等于,就入栈
  • 之前疑惑为什么宽度是 (i-stack.peek()-1) 不是 (i-topIndex) 就好了吗?还有为什么 stack 底部要放一个 -1?这两个问题的答案是一个,见后面

最后栈里可能还会剩下一些元素

  • 把此时栈顶的元素作为右边界 rightIndex = stack.peek(),topIndex = stack.pop() 栈顶依次出栈,area = heights[topIndex] * (rightIndex-stack.peek());

为什么宽度是 (i-stack.peek()-1) 不是 (i-topIndex) 就好了吗?还有为什么 stack 底部要放一个 -1?

一般是这样的,高度是栈顶元素 nums[6]=9,因为是单调递增栈,所以宽度顶多是 1(从图中也可以看出)

也就是宽度是:【当前加入的新元素下标 - 栈顶下一个元素的下标 -1】,好像直接用 【当前加入的新元素下标 - 栈顶下标】 替代也可以

但是也有这样的情况:

栈中仅剩元素 nums[3]=2,(想想 nums[3] = 2 让 0~2 的元素都出栈了,然后一直留到 nums[7] = 3 让 4~6 的元素都出栈了)

这个 nums[3]=2 的宽度显然可以达到 8 个,此时用【右边界下标 - 栈顶下标】= 7-3=4 计算是不正确的,只能计算到右半部分的面积,不能计算到之前已经全部出栈过的左半部分的面积

而用 【右边界下标 - 栈顶下一个元素的下标】=7-(-1)=8 则是正确的

class Solution {

    public int largestRectangleArea(int[] heights) {

        Stack<Integer> stack = new Stack();
        stack.push(-1);
        int res = 0;
        for (int i=0;i<heights.length;i++) {
            // 维持单调递增栈(可以平),比栈顶大或等才入栈
            if (stack.peek() == -1 || heights[i] >= heights[stack.peek()]) {
                // 栈里存的是元素的下标
                stack.push(i);
            }
            else {
                // 比栈顶小,就出栈
                while (stack.peek() != -1 && heights[i] < heights[stack.peek()]) {
                    int topIndex = stack.pop();
                    // 面积为 出栈元素的高度 * 到当前元素的距离
                    int area = heights[topIndex] * (i-stack.peek()-1);
                    if (area>res) {
                        res = area;
                    }
                }
                stack.push(i);
            }
        }

        // 最后可能还剩一个单调递增栈,[2(0),4(1),6(2)]
        int rightIndex = stack.peek();
        // 注意这里循环退出的条件不能是 i<stack.size(),因为stack一直在pop,size是在变化的
        for (int i=0;stack.peek()!=-1;i++) {
            int thisIndex = stack.pop();
            int area = heights[thisIndex] * (rightIndex - stack.peek());
            if (area > res) {
                res = area;
            }
        }
        return res;

    }
}

 

394. 字符串解码

class Solution {

    public String decodeString(String s) {
        StringBuffer res = new StringBuffer();
        StringBuffer curRes = new StringBuffer();
        Integer curMulti = 0;
        // 乘数栈
        Stack<Integer> multi_stack = new Stack();
        // 结果栈
        Stack<String> res_stack = new Stack();
        int i=0;
        while(i<s.length()) {
            Character c = s.charAt(i);
            // 是数字的话:读取后续所有连续的数字,
            if (isDigit(c)) {
                Integer digits = getDigits2(s, i);
                // 放到 curMulti 上 
                curMulti = digits;
                i+=digits.toString().length();
            }
            // 是字母的话:读取后续所有连续的字母,
            else if (isLetter(c)) {
                String letters = getLetters(s, i);
                // 附加到 curRes 上
                curRes.append(letters);
                i+=letters.length();
            }
            // 是[的话,curMulti curRes 直接推入栈
            else if (c == '[') {
                ++i;
                // curMulti curRes 直接推入栈
                multi_stack.push(curMulti);
                res_stack.push(curRes.toString());
                // curMulti curRes 清零
                curMulti=0;
                curRes=new StringBuffer();
            }
            else if (c == ']') {
                ++i;
                // 例:3[a2[c]]
                // [3,2] --> 2
                int multi = multi_stack.pop();
                StringBuffer sb = new StringBuffer();
                // curRes='c', sb='cc'
                for (int j=0;j<multi;j++) {
                    sb.append(curRes);
                }
                // [a] --> acc
                curRes = new StringBuffer(res_stack.pop()+sb);
            }
        }
        return curRes.toString();
    }
    

    private boolean isDigit(char c) {
        return c>='0' && c<='9';
    }
    private boolean isLetter(char c) {
        return (c>='a' && c<='z') || (c>='A' && c<='Z');
    }

    private String getDigits(String s, int startIndex) {
        int i=startIndex;
        StringBuffer sb = new StringBuffer();
        while(i<s.length() && isDigit(s.charAt(i))) {
            sb.append(s.charAt(i));
            i++;
        }
        return sb.toString();
    }

    private Integer getDigits2(String s, int startIndex) {
        int i=startIndex;
        StringBuffer sb = new StringBuffer();
        while(i<s.length() && isDigit(s.charAt(i))) {
            sb.append(s.charAt(i));
            i++;
        }
        return Integer.valueOf(sb.toString());
    }


    private String getLetters(String s, int startIndex) {
        int i=startIndex;
        StringBuffer sb = new StringBuffer();
        while(i<s.length() && isLetter(s.charAt(i))) {
            sb.append(s.charAt(i));
            i++;
        }
        return sb.toString();
    }

    private void pushLetters2Stack(Stack<String> stack, String letters) {
        if (!stack.isEmpty()) {
            if (isLetter(stack.peek().charAt(0))) {
                String tops = stack.pop();
                stack.push(tops+letters);
            }
        }
        stack.push(letters);
    }
}