栈与单调栈

栈是先进后出。

括号匹配是使用栈解决的经典问题。

20.有效的括号

1.字符串为奇数,肯定匹配不上,return false;

2.遍历字符串,当遇到左括号时,往栈中加入一个对应的右括号;

3.当遇到右括号时,判断:如果栈为空或是栈顶元素与当前字符不相等,则return false;否则从栈中弹出一个括号。

4.遍历结束后,判断栈是否为空,为空则true,否则false。

public boolean isValid(String s) {
        int n = s.length();
        if(n % 2 == 1){
            return false;
        }
        Deque<Character> stack = new LinkedList<Character>();
        for(int i=0; i< n; 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();
    }
  • 时间复杂度:O(n)。
  • 空间复杂度:O(n+∣Σ∣),其中 Σ 表示字符集,本题中字符串只包含6 种括号,栈中的字符数量为 O(n)。

155.最小栈

 要设计一个在常数时间内检索到最小元素的栈。

那么我们需要一个辅助栈,在每个元素a入栈时把当前栈的最小值 m 存储起来。在这之后无论何时,如果栈顶元素是 a,我们就可以直接返回存储的最小值 m

class MinStack {
    Deque<Integer> stack;
    Deque<Integer> minStack;
    public MinStack() {
        stack = new LinkedList<Integer>();
        minStack = new LinkedList<Integer>();
        // 注意,存放最小值的初始值一定要给定,否则添加第一个元素的时候,peek()会出错
        minStack.push(Integer.MAX_VALUE);
    }
    
    public void push(int val) {
        stack.push(val);
        minStack.push(Math.min(val, minStack.peek()));
    }
    
    public void pop() {
        stack.pop();
        minStack.pop();
    }
    
    public int top() {
       return stack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}
  • 时间复杂度:O(1)。因为栈的插入、删除与读取操作都是O(1) 。
  • 空间复杂度:O(n)。

394.字符串解码

题中可能出现括号嵌套的情况,比如 2[a2[bc]],这种情况下我们可以先转化成 2[abcbc],在转化成 abcbcabcbc。因此,把字母、数字和括号看成是独立的 TOKEN,并用栈来维护这些 TOKEN。

思路一:栈

具体的做法是,遍历这个栈:

1.如果当前的字符为数位,解析出一个数字(连续的多个数位)并进栈;

2.如果当前的字符为字母或者左括号,直接进栈;

3.如果当前的字符为右括号,开始出栈,一直到左括号出栈,出栈序列反转后拼接成一个字符串。此时取出栈顶的数字。

4.根据这个次数和字符串构造出新的字符串并进栈。

5.重复,最终将栈中的元素按照从栈底到栈顶的顺序拼接起来,就得到了答案。

思路2:递归

当遇到左括号时,后面的子问题可以再递归重复一遍,函数调用得到的字符串加上当前字符串就是结果。

class Solution {
    int ptr;
    String src;
    public String decodeString(String s) {
        src = s;
        ptr = 0;
        return getString();
    }

    public int getDigits() {
        int ret = 0;
        while (ptr < src.length() && Character.isDigit(src.charAt(ptr))) {
            ret = ret * 10 + src.charAt(ptr++) - '0';
        }
        return ret;
    }

    public String getString() {
        // 1.遇到右括号,弹出空串,与字符串组合
        if (ptr == src.length() || src.charAt(ptr) == ']') {
            // String -> EPS
            return "";
        }
        char cur = src.charAt(ptr);
        // 倍数
        int repTime = 1;
        // 结果字符串
        String ret = "";
        if (Character.isDigit(cur)) {
            // String -> Digits [ String ] String
            // 解析 Digits
            repTime = getDigits(); 
            // 过滤左括号
            ++ptr;
            // 解析 String
            String str = getString(); 
            // 过滤右括号
            ++ptr;
            // 构造字符串
            while (repTime-- > 0) {
                ret += str;
            }
        } else if (Character.isLetter(cur)) {
            // String -> Char String
            // 解析 Char
            ret = String.valueOf(src.charAt(ptr++));
        }
        
        return ret + getString();
    }

}
  • 时间复杂度:记解码后得出的字符串长度为 SSS,除了遍历一次原字符串 sss,我们还需要将解码后的字符串中的每个字符都拼接进答案中,故渐进时间复杂度为 O(S+∣s∣),即 O(S)。
  • 空间复杂度:若不考虑答案所占用的空间,那么就只剩递归使用栈空间的大小,这里栈空间的使用和递归树的深度成正比,最坏情况下为 O(∣s∣),故渐进空间复杂度为 O(∣s∣)。

好难好难!!!

739. 每日温度

通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。时间复杂度为O(n)。 

1.单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。

2.单调栈里只需要存放元素的下标i就可以了,如果需要使用对应的元素,直接T[i]就可以获取。

3.如果求一个元素右边第一个更大元素,单调栈的栈顶到栈底是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的。

4.主要有三个判断条件。

  • 当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况

 正向遍历温度列表。对于温度列表中的每个元素 temperatures[i],如果栈为空,则直接将 i 进栈,如果栈不为空,当 temperatures[i] > temperatures[stack.peek()],在res数组中存储 i-stack.peek(),并弹出栈顶元素,重复上述操作直到栈为空或者栈顶元素对应的温度小于等于当前温度,然后将 i 进栈。

 public int[] dailyTemperatures(int[] temperatures) {
        int n = temperatures.length;
        int[] res = new int[n];
        Deque<Integer> stack = new LinkedList<>();
        stack.push(0);
        for(int i = 1; i<n; i++){
            while(!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]){
                res[stack.peek()] = i-stack.peek();
                stack.pop();
            }
            stack.push(i);
        }
        return res;
    }
  • 时间复杂度:O(n)。
  • 空间复杂度:O(n)。

 84. 柱状图中最大的矩形

本题是要找每个柱子左右两边第一个小于该柱子的柱子,所以从栈头(元素从栈头弹出)到栈底的顺序应该是从大到小的顺序!

  • 弹出的都是比自己大的→\rightarrow→确定左边界;
  • 被弹出则是发生在第一次遇到比自己小的→\rightarrow→确定右边界

栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度

主要就是分析清楚如下三种情况:

  • 情况一:当前遍历的元素heights[i]大于栈顶元素heights[st.top()]的情况
  • 情况二:当前遍历的元素heights[i]等于栈顶元素heights[st.top()]的情况
  • 情况三:当前遍历的元素heights[i]小于栈顶元素heights[st.top()]的情况

注意:需要新建一个数组,在原height数组的前后,都加了一个元素0,是考虑了以下两种情况:

  • 如果数组本身就是升序的,例如[2,4,6,8],那么入栈之后 就是单调递减,一直都没有走 情况三 计算结果的哪一步,所以最后输出的就是0了。 如图:
  • 如果数组本身是降序的,例如 [8,6,4,2],在 8 入栈后,6 开始与8 进行比较,此时我们得到 mid(8),rigt(6),但是得不到 left边界。那么为了避免空栈取值,直接跳过了计算结果的逻辑。
public int largestRectangleArea(int[] heights) {
        int n=heights.length;
        int[] newHeight = new int[n+2];
        System.arraycopy(heights,0,newHeight,1,n);

        newHeight[0]=newHeight[n+1]=0;
        Deque<Integer> stack = new LinkedList<>();
        stack.push(0);
        int res = 0;
        for(int i=1; i< n+2; i++){
            while(!stack.isEmpty() && newHeight[i] < newHeight[stack.peek()]){
                int mid=stack.pop();
                int h = newHeight[mid];
                int w = i-stack.peek()-1;
                
                res=Math.max(res, w * h);
            }
            stack.push(i);
        }
        return res;
  • 时间复杂度:O(N)
  • 空间复杂度:O(N)
  • 30
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Python中,单调栈和单调队列是两种不同的数据结构。单调栈是一个,它的特点是内的元素是单调的,可以是递增或递减的。在构建单调栈时,元素的插入和弹出都是在的一端进行的。与此类似,单调队列也是一个队列,它的特点是队列内的元素是单调的,可以是递增或递减的。在构建单调队列时,元素的插入是在队列的一端进行的,而弹出则是选择队列头进行的。 单调队列在解决某些问题时,能够提升效率。例如,滑动窗口最大值问题可以通过使用单调队列来解决。单调队列的结构可以通过以下代码来实现: ```python class MQueue: def __init__(self): self.queue = [] def push(self, value): while self.queue and self.queue[-1 < value: self.queue.pop(-1) self.queue.append(value) def pop(self): if self.queue: return self.queue.pop(0) ``` 上述代码定义了一个名为MQueue的类,它包含一个列表作为队列的存储结构。该类有两个方法,push和pop。push方法用于向队列中插入元素,它会删除队列尾部小于插入元素的所有元素,并将插入元素添加到队列尾部。pop方法用于弹出队列的头部元素。 总结来说,单调栈和单调队列都是为了解决特定问题而设计的数据结构。单调栈在构建时元素的插入和弹出都是在的一端进行的,而单调队列则是在队列的一端进行的。在Python中,可以通过自定义类来实现单调队列的功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值