栈:删除最外层的括号 (Leetcode 1021 / 155 / 1172 / 剑指31 / 面试 03.03)
/** * 解法一:暴力解法思路分析 * 1.定义容器存储原语子串 * new ArrayList<String>(); * 2.定义左括号、右括号计数器: * int left = 0, right = 0; * 3.遍历字符串,读取到括号时对应计数器自增 * 4.检查是否到达原语结尾,截取原语子串并添加到容器中 * 5.遍历容器,删除最外层括号后合并成新串 * * 边界问题: * 遍历字符串,注意索引越界:i < S.length() * 截取原语字符串时,注意起止索引:[start, end) * 细节问题: * 需要记录上一次截取原语子串之后的位置 * 删除原语子串的最外层括号,其实就是重新截取 * @param S * @return */ public String removeOuterParentheses(String S) { int len = S.length(); // 1.定义容器存储原语子串 List<String> list = new ArrayList<>(); // 2.定义左括号、右括号计数器 int left = 0, right = 0, lastOpr = 0; // 3.遍历字符串,读取到括号时对应计数器自增 for (int i = 0; i < len; i++) { char c = S.charAt(i); if (c == '(') { left++; } else if (c == ')') { right++; } // 4.检查是否到达某个原语结尾,截取原语子串添加到容器 if (left == right) { list.add(S.substring(lastOpr, i + 1)); lastOpr = i + 1; } } // 5.遍历容器中的原语子串,删除最外层后合并成新串 StringBuilder sb = new StringBuilder(); for (String s : list) { sb.append(s.substring(1, s.length() - 1)); } return sb.toString(); }
/** * 解法二:优化解法思路分析 * 1.定义容器存储删除外层括号后的原语子串 * new StringBuilder(); * 2.定义左括号、右括号计数器: * int left = 0, right = 0; * 3.遍历字符串,读取到括号时对应计数器自增 * 4.检查是否到达原语结尾,截取不包含最外层的原语子串并拼接到容器中 * * @param S * @return */ public String removeOuterParentheses(String S) { int len = S.length(); // 1.定义容器存储删除外层括号后的原语子串 StringBuilder sb = new StringBuilder(); // 2.定义左括号、右括号计数器 int left = 0, right = 0, lastOpr = 0; // 3.遍历字符串,读取到括号时对应计数器自增 for (int i = 0; i < len; i++) { char c = S.charAt(i); if (c == '(') { left++; } else if (c == ')') { right++; } // 4.检查是否到达某个原语结尾,截取不包含最外层的原语子串添加到容器 if (left == right) { sb.append(S.substring(++lastOpr, i)); lastOpr = i + 1; } } return sb.toString(); }
/** * 栈:后进先出 * @param <E> */ class Stack<E> { Object[] elements = new Object[10000]; int index = -1; // 栈顶索引 int size = 0; // 栈中元素个数 public Stack() { } /** * 往栈顶插入元素 * @param c */ public void push(E c) { elements[++index] = c; size++; } /** * 从栈顶获取数据,不移出 栈解法优化,使用栈的思想,直接用数组取代栈: * @return */ public E peek() { if (index < 0) { return null; } return (E)elements[index]; } /** * 从栈顶移出元素 * @return */ public E pop() { E e = peek(); if (e != null) { elements[index] = null; // 移出动作 index--; // 栈顶下移 size--; } return e; } public boolean isEmpty() { return size == 0; } }
/** * 最优解:栈解法 * 1.使用数组模拟一个栈,临时存储左括号字符 * push(Character) ; pop(); isEmpty() * 2.遍历字符串,根据情况进行入栈/出栈操作 栈实现代码: * 读取到左括号,左括号入栈 * 读取到右括号,左括号出栈 * 3.判断栈是否为空,若为空,找到了一个完整的原语 * 4.截取不含最外层括号的原语子串并进行拼接 * * @param S * @return */ public String removeOuterParentheses(String S) { StringBuilder result = new StringBuilder(); // 1.使用数组模拟一个栈,临时存储字符,替代计数器 Stack stack = new Stack(); int lastOpr = 0; // 2.遍历字符串,根据情况进行入栈 / 出栈操作 for (int i = 0; i < S.length(); i++) { char ch = S.charAt(i); if (ch == '(') { // 遇到左括号,左括号入栈 stack.push(ch); } else if (ch == ')') { // 遇到右括号,左括号出栈 stack.pop(); // 栈顶的左括号出栈 } // 3.判断栈是否为空,若为空,找到了一个完整的原语 if (stack.isEmpty()) { // 4.截取不含最外层括号的原语子串并进行拼接 result.append(S.substring(lastOpr + 1, i));// 去掉原语的最外层括号并追加到结果 lastOpr = i + 1;// 往后找,再次初始化原语开始位置 } } return result.toString(); }
/** * 最优解:代码优化: * 1.直接用数组取代栈 * 创建数组、栈顶索引,使用数组操作入栈和出栈 * 2.将原字符串转为数组进行遍历 * char[] s = S.toCharArray(); * 3.去掉截取子串的操作,将原语字符直接拼接 * 读取到左括号:此前有数据,当前必属原语 * 读取到右括号:匹配后不为0,当前必属原语 * @param S * @return */ public String removeOuterParentheses(String S) { StringBuilder result = new StringBuilder(); // 1.直接用数组取代栈 int index = -1; // 栈顶索引 int len = S.length(); char[] stack = new char[len]; char[] s = S.toCharArray(); // 2.遍历字符串,根据情况进行入栈 / 出栈操作 for (int i = 0; i < len; i++) { char ch = s[i]; if (ch == '(') { // 遇到左括号,左括号入栈 // 3.去掉截取子串的操作,将原语字符直接拼接 if (index > -1) { // 此前有数据,当前必属原语 result.append(ch); } stack[++index] = ch; } else /*if (ch == ')')*/ { // 遇到右括号,左括号出栈 stack[index--] = '\u0000'; // 栈顶的左括号出栈 if (index > -1) { result.append(ch); } } } return result.toString(); }
最最优解
Leetcode 155 - 最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
- push(x) —— 将元素 x 推入栈中。
- pop() —— 删除栈顶的元素。
- top() —— 获取栈顶元素。
- 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.
提示:
- pop、top 和 getMin 操作总是在 非空栈 上调用。
//思路:使用两个队列实现最小栈 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( minStack.peek()< x? minStack.peek():x); //将当前最小值入栈,为保证数量一致,所以会重复存放最小值 } public void pop() { xStack.pop();//出栈 minStack.pop();//当前最小值也出栈 } public int top() { return xStack.peek();//获取栈顶元素 } public int getMin() { 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.getMin(); */
剑指Offer 31 - 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。
假设压入栈的所有数字均不相等。
例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,
但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
提示:
0 <= pushed.length == popped.length <= 1000
0 <= pushed[i], popped[i] < 1000
pushed 是 popped 的排列。
//思路:每次比较栈顶元素与弹出序列是否一致,一致则出栈 class Solution { public boolean validateStackSequences(int[] pushed, int[] popped) { Stack<Integer> stack = new Stack<>(); int i = 0; for(int num : pushed) { stack.push(num); // num 入栈 while(!stack.isEmpty() && stack.peek() == popped[i]) { // 循环判断与出栈 stack.pop(); i++; } } return stack.isEmpty(); } }
堆盘子 - 面试 03.03
堆盘子。设想有一堆盘子,堆太高可能会倒下来。
因此,在现实生活中,盘子堆到一定高度时,我们就会另外堆一堆盘子。请实现数据结构SetOfStacks,模拟这种行为。
SetOfStacks应该由多个栈组成,并且在前一个栈填满时新建一个栈。
此外,SetOfStacks.push()和SetOfStacks.pop()应该与普通栈的操作方法相同(也就是说,pop()返回的值,应该跟只有一个栈时的情况一样)。
进阶:实现一个popAt(int index)方法,根据指定的子栈,执行pop操作。
当某个栈为空时,应当删除该栈。当栈中没有元素或不存在该栈时,pop,popAt 应返回 -1.
示例1:
输入:
["StackOfPlates", "push", "push", "popAt", "pop", "pop"]
[[1], [1], [2], [1], [], []]
输出:
[null, null, null, 2, 1, -1]
示例2:
输入:
["StackOfPlates", "push", "push", "push", "popAt", "popAt", "popAt"]
[[2], [1], [2], [3], [0], [0], [0]]
输出:
[null, null, null, null, 2, 1, 3]
class StackOfPlates { private List<Stack<Integer>> stackList; private int cap; public StackOfPlates(int cap) { stackList = new ArrayList<>(); //使用ArrayList来存储所有的栈 this.cap = cap; } public void push(int val) { if (cap <= 0) {//处理边界 return; } if (stackList.isEmpty() || stackList.get(stackList.size() - 1).size() == cap) {//处理最后一个栈满的情况 Stack<Integer> stack = new Stack<>(); stack.push(val); stackList.add(stack); return; } stackList.get(stackList.size() - 1).push(val); } public int pop() { return popAt(stackList.size() - 1); } public int popAt(int index) { if (index < 0 || index >= stackList.size()) { return -1; } Stack<Integer> stack = stackList.get(index); if (stack.isEmpty()) { return -1; } int res = stack.pop(); if (stack.isEmpty()) { stackList.remove(index); } return res; } }
餐盘栈 - Leetcode 1172
我们把无限数量 ∞ 的栈排成一行,按从左到右的次序从 0 开始编号。每个栈的的最大容量 capacity 都相同。
实现一个叫「餐盘」的类 DinnerPlates:
DinnerPlates(int capacity) - 给出栈的最大容量 capacity。
void push(int val) - 将给出的正整数 val 推入 从左往右第一个 没有满的栈。
int pop() - 返回 从右往左第一个 非空栈顶部的值,并将其从栈中删除;如果所有的栈都是空的,请返回 -1。
int popAtStack(int index) - 返回编号 index 的栈顶部的值,并将其从栈中删除;如果编号 index 的栈是空的,请返回 -1。
示例:
输入:
["DinnerPlates","push","push","push","push","push","popAtStack","push","push","popAtStack","popAtStack","pop","pop","pop","pop","pop"]
[[2],[1],[2],[3],[4],[5],[0],[20],[21],[0],[2],[],[],[],[],[]]
输出:
[null,null,null,null,null,null,2,null,null,20,21,5,4,3,1,-1]
解释:
DinnerPlates D = DinnerPlates(2); // 初始化,栈最大容量 capacity = 2
D.push(1);
D.push(2);
D.push(3);
D.push(4);
D.push(5); // 栈的现状为:
2 4
1 3 5
﹈ ﹈ ﹈
D.popAtStack(0); // 返回 2。栈的现状为:
4
1 3 5
﹈ ﹈ ﹈
D.push(20); // 栈的现状为:
20 4
1 3 5
﹈ ﹈ ﹈
D.push(21); // 栈的现状为:
20 4 21
1 3 5
﹈ ﹈ ﹈
D.popAtStack(0); // 返回 20。栈的现状为:
4 21
1 3 5
﹈ ﹈ ﹈
D.popAtStack(2); // 返回 21。栈的现状为:
4
1 3 5
﹈ ﹈ ﹈
D.pop() // 返回 5。栈的现状为:
4
1 3
﹈ ﹈
D.pop() // 返回 4。栈的现状为:
1 3
﹈ ﹈
D.pop() // 返回 3。栈的现状为:
1
﹈
D.pop() // 返回 1。现在没有栈。
D.pop() // 返回 -1。仍然没有栈。
//采用双指针思想,后面后讲到的 class DinnerPlates { private int capacity = 0; private int left = 0;//从左边开始第一个不满的,严格控制 private int right = 0;//从右边开始第一个非空的,宽松 private List<Stack<Integer>> list = new ArrayList<>(); public DinnerPlates(int capacity) { this.capacity = capacity; Stack<Integer> stack2 = new Stack(); list.add(stack2); } /* 直接在left所在位置进行入栈,push后需要遍历查找下一个未满栈 细节问题: 1. 需要对list进行扩容操作 2. 需要更新right位置(right宽松,不保证一定是非空,可以在第一个非空的右边) */ public void push(int val) { if (left < 0) left = 0; Stack<Integer> stack = list.get(left); stack.push(val); //达到容量,则找后面的第一个未满栈 while (left < list.size() && list.get(left).size() == capacity) { left++; } if (left >= list.size()) { //需要增加栈的数量 Stack<Integer> stack2 = new Stack(); list.add(stack2); } if (right < left) { //新的栈在right以右,更新right的位置 right = left; } } /* 以right为起点,往左遍历查找第一个非空栈 细节问题: 移除数据后,需要判断所在位置是不是比left更小,如果满足则需要更新left 移除数据后,需要判断所在位置是不是空栈,空栈则将right左移一个(right宽松,不保证一定是非空,可以在第一个非空的右边) */ public int pop() { for (int i = right; i >= 0; i--) {//从right开始找第一个非空栈,乐观情况right就是想要的位置 Stack<Integer> stack = list.get(i); if (!stack.isEmpty()) { right = i; Integer val = stack.pop(); if (left > right) { //如果移除的位置在left左边,需要将left修复到right位置 left = right; } if (stack.isEmpty()) {//pop之后,right需要再向左移动一个(这里不保证right的新位置一定为非空栈) right--; } return val; } } return -1; } /* 移除数据后,需要判断所在位置是不是比left更小,如果满足则需要更新left */ public int popAtStack(int index) { if (index > list.size() - 1) //越界检查 return -1; Stack<Integer> stack = list.get(index); if (!stack.isEmpty()) { //栈不为空 if (left > index) { //在左边界之前,更新左边界执行更靠左的未满栈 left = index; } return stack.pop(); } else { return -1; } } } /** * Your DinnerPlates object will be instantiated and called as such: * DinnerPlates obj = new DinnerPlates(capacity); * obj.push(val); * int param_2 = obj.pop(); * int param_3 = obj.popAtStack(index); */