算法通关村——栈的经典算法问题解析

栈的经典算法问题

1、括号匹配问题

1.1、问题介绍

本题出自LeetCode20:

给定一个只包括 ‘(’ , ‘)’ , ‘{’ , ‘}’ , ‘[’ , ‘]’ 的字符串s,判断字符串是否有效。有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合
  • 左括号必须以正确的顺序闭合

示例:

输入:s = “()[]{}”

输出:true

1.2、问题解答

本题比较麻烦的地方是如何判断两个符号是不是一组的,我们可以用哈希表将所有符号先存储,左半边做key,右半边做value。遍历字符串的时候,遇到左半边符号就入栈,遇到右半边符号就与栈顶的符号比较,不匹配就返回false。

代码实现如下:

boolean isValid(String s) {
    if(s.length() <= 1) {
        return false;
    }
    
    Map<Character, Character> smap = new HashMap<>();
    smap.put('(', ')');
    smap.put('[', ']');
    smap.put('{', '}');
    
    Stack<Character> stack = new Stack<>();
    
    for(int i = 0; i < s.length(); i++){
        char item = s.chatAt(i);
        if (smap.containsKey(item)){
            stack.push(item);
        } else {
            if (!stack.isEmpty()) {
                Character left = stack.pop();
                char rightchar = smap.get(left);
                if (rightchat != item){
                    return false;
                }
            } else {
                return false;
            }
        }
    }
    return stack.isEmpty();
}

2、最小栈

2.1、问题介绍

本题出自LeetCode155. 设计一个支持push、pop、top操作,并能在常数时间内检索到最小元素的栈。

实现MinStack类:

  • MinStack() 初始化堆栈对象
  • void push(int val) 将元素val推入堆栈
  • void pop() 删除堆栈顶部的元素
  • int top() 获取堆栈顶部的元素
  • int getMin() 获取堆栈中的最小元素

示例:

算法通关村——栈的经典算法问题解析_第1张图片

2.2、问题解答

本题可以借用另外一个栈,来存储栈内的最小值,如下图所示:

算法通关村——栈的经典算法问题解析_第2张图片

图中左侧的栈中存储栈内的实际值,右侧的Min栈中存储元素入栈时栈内的最小值。

因为题目要求在常数时间内获得栈中的最小值,因此不能在getMin()的时候再去计算最小值,最好应该再push或者pop的时候就计算好了当前栈中的最小值。

对于栈来说,如果一个元素a在入栈的时候,栈内有其他元素b,c,d,那么无论这个栈在之后经历什么操作,只要a在栈中,b,c,d就一定在栈中,因为在a被弹出之前,b,c,d不会被弹出。

因此,在操作过程中的任意一个时刻,只要栈顶的元素是a,那么我们就可以确定栈里面现在的元素一定是a,b,c,d。只要在每个元素a入栈之前把当前栈的最小值m存储起来。在这之后无论何时,如果栈顶的元素是a,就可以直接返回存储的最小值m。

因此可以只用一个辅助栈,与元素栈同步插入和删除,用于存储与每个元素对应的最小值,使得每个元素a与其相应的最小值m时刻保持一一对应。

具体的思路如下:

  • 当一个元素要入栈时,我们取当前辅助栈的栈顶存储的最小值,与当前元素比较得出最小值,将这个最小值插入辅助栈中;
  • 当一个元素要出栈时,我们把辅助栈的栈顶元素也一并弹出;

这样在任意时刻,栈内元素的最小值就存储在辅助栈的栈顶元素中。

具体的实现代码如下:

class MinStack {
    Deque<Integer> xStack;
    Deque<Integer> minStack;
    
    public MinStack() {
        xStack = new LinkedList<Integer>();
        minStack = new LinkedList<Integer>();
        minStack.push(Integer.MAX_VALUE);
    }
    
    public void push(int x) {
        xStack.push(x);
        minStack.push(Math.min(minStack.peek(), x));
    }
    
    public void pop() {
        xStack.pop();
        minStack.pop();
    }
    
    public int top() {
        return xStack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}

3、最大栈

3.1、问题介绍

本题来自LeetCode 716. 设计一个最大栈数据结构,既支持栈操作,又支持查找栈中最大元素。

实现MaxStack类:

  • MaxStack() 初始化栈对象
  • void push(int x) 将元素x压入栈中
  • int pop() 移除栈顶元素并返回这个元素
  • int top() 返回栈顶元素,无需移除
  • int peekMax() 检索并返回栈中最大元素,无需移除
  • int popMax() 检索并返回栈中最大元素,并将其移除。如果有多个最大元素,只要移除最靠近栈顶的那个。

3.2、问题解答

本题与最小栈刚好相反,处理的方法是一样的,这里只讲最后一种popMax方法。

对于popMax(),由于我们知道当前栈中最大的元素值,因此可以直接将两个栈同时出栈,并存储第一个栈出栈的所有制。当某个时刻,第一个栈的出栈元素等于当前栈中最大的元素值时,就找到了最大值。此时在将之前第一个栈出栈的所有元素重新入栈,并同步更新第二个栈,就完成了popMax()操作。

代码实现如下:

public int popMax() {
    int max = peekMax();
    Deque<Integer> buffer = new LinkedList<Integer>();
    while(top() != max) {
        buffer.push(pop());
    }
    //循环结束后,此时的栈顶元素就是栈内的最大元素,将其出栈
    pop();
    while(!buffer.isEmpty()) {
        push(buffer.pop());
    }
    return max;
}

4、总结

栈的使用并不复杂,在使用的过程中只用记住栈的特点——先进后出就可以了。

你可能感兴趣的:(栈,算法,java,数据结构)