精解括号匹配问题与极致栈设计:揭开最大栈和最小栈的奥秘

简介: 精解括号匹配问题与极致栈设计:揭开最大栈和最小栈的奥秘


最大栈和最小栈是极致栈的两个重要变种。最大栈用于存储当前匹配的最大值,而最小栈用于存储当前匹配的最小值。

括号匹配问题

这个问题我们来看力扣20题的描述:

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

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = “()”

输出:true

示例 2:

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

输出:true

示例 3:

输入:s = “(]”

输出:false

对于这个题我们有两种解决思路:

1.我们用哈希表把所有符号先存储起来,左边符号作key,右边符号作value。遍历字符串的时候,遇见左边符号就入栈,遇见右边符号就与栈顶的符号进行比较,不匹配就返回false。

public boolean isValid(String s) {
        //获取字符串长度
        int n = s.length();
        //如果字符串长度为奇数,则返回false
        if (n % 2 == 1) {
            return false;
        }
        //创建一个HashMap,用于存储字符串中的括号
        Map<Character, Character> map = new HashMap<>();
        map.put('[', ']');
        map.put('(', ')');
        map.put('{', '}');
        //创建一个栈,用于存储字符串中的括号
        Stack<Character> stack = new Stack<>();
        //遍历字符串中的每一个字符
        for (int i = 0; i < s.length(); i++) {
            char item = s.charAt(i);
            //如果字符串中的字符在HashMap中存在,则将其压入栈中
            if (map.containsKey(item)) {
                stack.push(item);
            } else {
                //如果栈不为空,则弹出栈顶元素,如果弹出的元素与当前字符串中的字符不匹配,则返回false
                if (stack.isEmpty() == false) {
                    char pop = stack.pop();
                    if (map.get(pop) != item) {
                        return false;
                    }
                } else {
                    return false;
                }
            }
        }
        //如果栈为空,则返回true,否则返回false
        return stack.isEmpty();
    }
  1. 单纯的使用栈,如果遇见左边符号直接压入栈中,遇见右边的符号是先判断栈是否为空,为空则返回false,不为空则弹出栈顶元素,如栈顶元素不为相匹配的左边符号则直接返回false,最后元素遍历完返回栈是否为空。
public boolean isValid(String s) {
        int n = s.length();
        // 如果字符串长度为奇数,则直接返回false
        if (n % 2 == 1) {
            return false;
        }
        // 创建一个栈
        Deque<Character> stack = new LinkedList<>();
        // 遍历字符串
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            // 如果当前字符为左括号,则将其压入栈中
            if (c == '(' | c == '[' || c == '{') {
                stack.push(c);
            // 如果当前字符为右括号,则从栈中弹出一个元素,如果弹出的元素与当前字符不匹配,则返回false
            } else if (c == '}' || c == ']' || c == ')') {
                if (stack.isEmpty()) {
                    return false;
                }
                char top = stack.pop();
                if ((top != '(' && c == ')') || (top != '{' && c == '}') || (top != '[' && c == ']')) {
                    return false;
                }
            }
        }
        // 如果栈为空,则返回true,否则返回false
        return stack.isEmpty();
    }

最小栈

我们来看力扣155题的描述:

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

实现 MinStack 类:

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

这个题通俗的理解就是给栈提供一个能获取最小元素的方法并且要在常数时间内。

我们可以设计一个辅助栈,与元素栈同步插入与删除,用于存储每个元素入栈时的最小值(也就是说在辅助栈中我们每次插入的是元素栈中的最小值)

  • 当一个元素要入栈时,我们取当前辅助栈的栈顶存储的最小值,与当前要入栈的元素中的最小值插入辅助栈中。
  • 当一个元素要出栈时,我们把辅助栈的栈顶元素也一并弹出。
    我们来看具体实现代码:
class MinStack {
    // 定义两个双端队列,分别存放输入的值和当前的最小值
    Deque<Integer> xStack;
    Deque<Integer> minStack;
    public MinStack() {
        // 初始化双端队列
        xStack = new LinkedList<>();
        minStack = new LinkedList<>();
        // 第一个最小值设置为最大值
        minStack.push(Integer.MAX_VALUE);
    }
    public void push(int val) {
        // 输入一个值
        xStack.push(val);
        // 将当前的最小值和输入的值比较,取较小的值
        minStack.push(Math.min(minStack.peek(), val));
    }
    public void pop() {
        // 弹出双端队列的最后一个值
        xStack.pop();
        minStack.pop();
    }
    public int top() {
        // 返回双端队列的最后一个值
        return xStack.peek();
    }
    public int getMin() {
        // 返回双端队列的最小值
        return minStack.peek();
    }
}

最大栈

跟最小栈实现方法类似寻找最大值。

需要注意的就是最后一个方法,弹出最大值,具体就是拿到最大元素,然后在数字栈中把最大值以上的元素全部弹出存储在新建的栈中,然后弹出最大值,最后把新建的栈中的元素重新压入数字栈中。

由于力扣最大栈是VIP题目,我们可以尝试一下牛客的最大栈问题

class MaxStack {
    // 定义两个栈,一个用来存储数字,另一个用来存储最大值
    Deque<Integer> xStack;
    Deque<Integer> maxStack;
    public MaxStack() {
        // 初始化两个栈
        xStack = new LinkedList<>();
        maxStack = new LinkedList<>();
    }
    public void push(int val) {
        // 获取当前最大值,如果栈为空,则最大值为当前值
        int max = maxStack.isEmpty() ? val : maxStack.peek();
        // 比较当前值和最大值,取最大值
        max = max > val ? max : val;
        // 将值和最大值分别压入栈中
        xStack.push(val);
        maxStack.push(max);
    }
    public int pop() {
        // 弹出最大值栈顶元素
        maxStack.pop();
        // 弹出数字栈顶元素
        return xStack.pop();
    }
    public int top() {
        // 返回数字栈顶元素
        return xStack.peek();
    }
    public int peekMax() {
        // 返回最大值栈顶元素
        return maxStack.peek();
    }
    public int popMax() {
        // 获取最大值栈顶元素
        int max = peekMax();
        // 创建一个栈
        Stack<Integer> stack = new Stack<>();
        // 当栈顶元素不等于最大值时,将栈顶元素压入栈中
        while (top() != max) {
            stack.push(pop());
        }
        // 弹出数字栈顶元素
        pop();
        // 将栈中的元素弹出,压入数字栈中
        while (!stack.isEmpty()) {
            push(stack.pop());
        }
        // 返回最大值
        return max;
    }
}
相关文章
|
4月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
75 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
9月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
365 77
|
8月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
189 11
|
8月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
9月前
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
292 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
9月前
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
153 9
|
9月前
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
204 7
|
11月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
317 5
|
11月前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
256 0

热门文章

最新文章