栈的经典算法问题-算法通关村
1.括号匹配问题
-
LeetCode20:给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串s,判断字符串是否有效。有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
-
示例1: 输入: s = “() [] {}” 输出:true -
本题比较麻烦的是如何判断两个符号是不是一组的,我们可以用哈希表将所有符号先存储,左半边做key,右半边做value。遍历字符串的时候,遇到左半边符号就入栈,遇到右半边符号就与栈顶的符号比较,不匹配就返回false。
-
public static boolean isValid(String str){ if(str.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 < str.length(); i++){ char item = str.charAt(i); if(smap.containsKey(item)){ stack.push(item); }else{ if(!stack.isEmpty()){ Character left = stack.pop(); char rightChar = smap.get(left); if(rightChar != item){ return false; } }else{ return false; } } } //循环结束后,若栈空则stak是括号有效的字符串,否则说明还有左括号没匹配 return stack.isEmpty(); }
-
LeetCode给我们造了十几个括号匹配的问题,都是条件变来变去,但是解决起来有难有易,如果你感兴趣,可以继续研究一下:Leetcode20 有效的括号、LeetCode22.括号生成、LeetCode32.最长有效括号、LeetCode301.删除无效的括号和leetcode 856 括号的分数等。
2最小栈
-
LeetCode 155:设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
-
实现MinStack类:
-
MinStack() 初始化堆栈对象 void push(int val) 将元素val推入堆栈 void pop() 删除堆栈顶部的元素 int top() 获取堆栈顶部的元素 int getMin() 获取堆栈中的最小元素 -
示例:
-
输入: [“MinStack”,“push”,“push”,“push”,“getMin”,“pop”,“top”,“getMin”] [[],[-2],[0],[-3],[],[],[],[]] 输出: [null,null,null,null,-3,null,0,-2] 解释: MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.getMin(); --> 返回 -3. minStack.pop(); minStack.top(); --> 返回 0. minStack.getMin(); --> 返回 -2. -
-
题目要求在常数时间内获得栈中的最小值,因此不能在 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 ArrayDeque<Integer>(); minStack = new ArrayDeque<Integer>(); //Integer.MAX_VALUE是一个Java中整数类型Integer的最大值。 // 它的值是2^31 - 1,即2147483647。 //以便后续的push方法能够正确更新最小值 minStack.push(Integer.MAX_VALUE); } public void push(int x){ xStack.push(x); //比较栈中最小值和待压入元素x的大小,并将较小的值压入minStack栈中。 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(); } }
-
不知道有没有小伙伴和我有一样的疑问,创建栈为什么不用 Stack,而是用 Duque(Java 推荐使用)。
-
Deque是Java中的双端队列(Double Ended Queue)接口,它可以在队列的两端进行插入和删除操作。
Deque接口继承自Queue接口,它定义了许多用于操作双端队列的方法,包括插入、删除、查找和访问元素等操作。常用的方法包括:
- addFirst(element):在双端队列的头部插入元素。
- addLast(element):在双端队列的尾部插入元素。
- removeFirst():删除并返回双端队列的头部元素。
- removeLast():删除并返回双端队列的尾部元素。
- getFirst():返回双端队列的头部元素,但不从队列中删除。
- getLast():返回双端队列的尾部元素,但不从队列中删除。
- size():返回双端队列中的元素个数。
Deque接口的常见实现类包括:
-
LinkedList:使用链表实现的双端队列,可以作为栈和队列来使用。
-
Deque<Integer> xStack; Deque<Integer> minStack; public MinStack(){ xStack = new LinkedList<>(); minStack = new LinkedList<>(); }
-
ArrayDeque:使用动态数组实现的双端队列,效率比LinkedList略高。
使用Deque接口,可以灵活地**实现先进先出队列(FIFO)、后进先出栈(LIFO)**以及双向队列的功能,具体实现可以根据需求选择合适的实现类。
3最大栈
-
LeetCode716: 设计一个最大栈数据结构,即支持栈操作,又支持查找栈中最大元素。
-
实现MaxStack类:
-
MaxStack()
初始化栈对象void push(int x)
将元素 x 压入栈中。int pop()
移除栈顶元素并返回这个元素。int top()
返回栈顶元素,无需移除。int peekMax()
检索并返回栈中最大元素,无需移除。int popMax()
检索并返回栈中最大元素,并将其移除。如果有多个最大元素,只要移除 最靠近栈顶 的那个。 -
示例:
-
输入: [“MaxStack”, “push”, “push”, “push”, “top”, “popMax”, “top”, “peekMax”, “pop”, “top”] [[], [5], [1], [5], [], [], [], [], [], []] 输出: [null, null, null, null, 5, 5, 1, 5, 1, 5] 解释: MaxStack stk = new MaxStack(); stk.push(5); // [5] - 5 既是栈顶元素,也是最大元素 stk.push(1); // [5, 1] - 栈顶元素是 1,最大元素是 5 stk.push(5); // [5, 1, 5] - 5 既是栈顶元素,也是最大元素 stk.top(); // 返回 5,[5, 1, 5] - 栈没有改变 stk.popMax(); // 返回 5,[5, 1] - 栈发生改变,栈顶元素不再是最大元素 stk.top(); // 返回 1,[5, 1] - 栈没有改变 stk.peekMax(); // 返回 5,[5, 1] - 栈没有改变 stk.pop(); // 返回 1,[5] - 此操作后,5 既是栈顶元素,也是最大元素 stk.top(); // 返回 5,[5] - 栈没有改变 -
class MaxStack { Deque<Integer> xStack; Deque<Integer> maxStack; public MaxStack(){ xStack = new ArrayDeque<Integer>(); maxStack = new ArrayDeque<Integer>(); //以便后续的push方法能够正确更新最大值 maxStack.push(Integer.MIN_VALUE); } public void push(int x){ xStack.push(x); //比较栈中最小值和待压入元素x的大小,并将较大的值压入minStack栈中。 maxStack.push(Math.max(maxStack.peek(), x)); } public int pop(){ maxStack.pop(); return xStack.pop(); } public int top(){ return xStack.peek(); } public int popMax(){ int max = peekMax(); Deque<Integer> ds = new ArrayDeque(); while(top() != max){ ds.push(pop()); } pop(); while(!ds.isEmpty()){ push(ds.pop()); } return max; } public int peekMax(){ return maxStack.peek(); } } ``