Leetcode 1021 删除最外层的括号
有效括号字符串为空 ("")、"(" + A + “)” 或 A + B,其中 A 和 B 都是有效的括号字符串,+ 代表字符串的连接。例如,"","()","(())()" 和 “(()(()))” 都是有效的括号字符串。
如果有效字符串 S 非空,且不存在将其拆分为 S = A+B 的方法,我们称其为原语(primitive),其中 A 和 B 都是非空有效括号字符串。
给出一个非空有效字符串 S,考虑将其进行原语化分解,使得:S = P_1 + P_2 + … + P_k,其中 P_i 是有效括号字符串原语。
对 S 进行原语化分解,删除分解中每个原语字符串的最外层括号,返回 S 。
示例一:
输入:"( ( ) ( ) ) ( ( ) )"
输出:"( )( )( )"
解释:
输入字符串为 “( ( ) ( ) ) (( ) )”,原语化分解得到 “( ( ) ( ) )” + “( ( ) )”,
删除每个部分中的最外层括号后得到 “( )( )” + “( )” = “( ) ( )( )”。
示例二:
输入:"( ( ) ( ) ) ( ( ) ) ( ( ) ( ( ) ) )"
输出:"( ) ( ) ( ) ( ) ( ( ) )"
解释:
输入字符串为 “( ( ) ( ) ) ( ( ) ) ( ( ) ( ( ) ) )”,原语化分解得到 “( ( ) () )” + “( ( ) )” + “( ( ) ( ( ) ) )”,
删除每个部分中的最外层括号后得到 “( ) ()” + “( )” + “( ) ( ( ) )” = “( ) ( ) ( ) ( ) ( ( ) )”。
提示:
- S.length <= 10000
- S[i] 为 “(” 或 “)”
- S 是一个有效括号字符串
解法一:暴力解法:拆解原语后删外层括号
思路:
- 1.定义容器存储原语子串 new ArrayList();
- 2.定义左括号、右括号计数器:int left = 0, right = 0;
- 3.遍历字符串,读取到括号时对应计数器自增
- 4.检查是否到达原语结尾,截取原语子串并添加到容器中
- 5.遍历容器,删除最外层括号后合并成新串
public String removeOuterParentheses(String S){
int len = S.length();
//定义容器存储原语字串
List<String> list = new ArrayList<>();
//定义左括号、右括号计数器
int right = 0,left = 0,lastOpr = 0;
//遍历字符串,读取到括号时对应计数器自增
for(int i = 0;i < len; ++i){
char c = S.charAt(i);
if(c == '('){
left++;
}else if(c ==')'){
right++;
}
//检查是否到达某个原语结尾,截取原语子串添加到容器
if(left ==right){
list.add(S.substring(lastOpr, i + 1));
lastOpr = i + 1;
}
}
//public String substring(int beginIndex, int endIndex)
//返回一个新字符串,它是此字符串的一个子字符串。
//该子字符串从指定的 beginIndex 处开始,直到索引 endIndex - 1 处的字符。
//所以此处i+1
//遍历容器中的原语子串,删除最外层括号后合并成新串
StringBuilder result = new StringBuilder();
for(String s : list){
result.append(s.substring(1,s.length()-1));
}
return result.toString();
}
解法二:优化解法:直接定位原语内层子串
public String removeOuterParentheses(String S){
int len = S.length();
//定义容器存储删除外层括号后的原语子串
StringBuilder result = new StringBuilder();
//定义左括号、右括号计数器
int left = 0,right = 0,lastOpr = 0;
//遍历字符串,读取到括号时对应计数器自增
for(int i = 0;i < len;++i){
char ch = S.charAt(i);
if(ch =='('){
left++;
}else if(ch == ')'){
right++;
}
if(left == right){
result.append(S.substring(++lastOpr,i));
lastOpr=i+1;
}
}
return result.toString();
}
解法三:栈解法
思路:
- 遍历字符串,根据情况进行入栈/出栈操作栈实现代码: 读取到左括号,左括号入栈;读取到右括号,左括号出栈
- 判断栈是否为空,若为空,找到了一个完整的原语
- 截取不含最外层括号的原语子串并进行拼接
public String removeOuterParentheses(String S) {
StringBuilder result = new StringBuilder();
Stack stack = new Stack();
int lastOpr = 0;
//遍历字符串,根据情况进行出/入栈操作
for(int i = 0;i < S.length();++i){
char ch = S.charAt(i);
if(ch == '('){
stack.push(ch);
}else if(ch == ')'){
stack.pop();
}
//判断栈是否为空,若为空,则找到了一个完整的原语
if(stack.isEmpty()){
result.append(S.substring(++lastOpr,i));
lastOpr = i + 1;
}
}
return result.toString();
}
解法四:栈解法优化
思路:
- 直接用数组取代栈:创建数组、栈顶索引,使用数组操作入栈和出栈
- 将原字符串转为数组进行遍历
char[] s = S.toCharArray();
,避免多次调用charAt()
方法 - 去掉截取子串的操作,将原语字符直接拼接
- 读取到左括号:此前有数据,当前必属原语
- 读取到右括号:匹配后不为0,当前必属原语
public String removeOuterParentheses(String S) {
StringBuilder result = new StringBuilder();
//直接用数组代替栈
int index = -1;
int len = S.length();
char[] stack = new char[len];
char[] s = S.toCharArray();
//遍历字符数组,根据情况进行出/入栈操作
for(int i = 0;i < len;++i){
char ch = s[i];
if(ch=='('){//去掉截取子串的操作,将原语字符直接拼接
if(index > -1){
result.append(ch);
}
stack[++index] = ch;
}else{//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
解法一:辅助栈
思路:
可以用两个栈,一个栈去保存正常的入栈出栈的值,另一个栈去存最小值,也就是用栈顶保存当前所有元素的最小值。存最小值的栈的具体操作流程如下:
- 将第一个元素入栈。
- 新加入的元素如果大于栈顶元素,那么新加入的元素就不处理。
- 新加入的元素如果小于等于栈顶元素,那么就将新元素入栈。
- 出栈元素不等于栈顶元素,不操作。
- 出栈元素等于栈顶元素,那么就将栈顶元素出栈。
class MinStack {
private Stack<Integer> stack;
private Stack<Integer> minStack;
public MinStack() {
stack = new Stack<>();
minStack = new Stack<>();
}
public void push(int x) {
stack.push(x);
if (!minStack.isEmpty()) {
int top = minStack.peek();
//小于的时候才入栈
if (x <= top) {
minStack.push(x);
}
}else{
minStack.push(x);
}
}
public void pop() {
int pop = stack.pop();
int top = minStack.peek();
//等于的时候再出栈
if (pop == top) {
minStack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
解法二:不借助栈
思路:
- 解法一中单独用了一个栈去保存所有最小值,那现在只用一个变量去保存最小值
如果把min
更新为2
,那么之前的最小值3
就丢失了。如何把3
保存起来呢?把它在2
之前压入栈中即可。
上边的最后一个状态,当出栈元素是最小元素时,我们只需要把2
出栈,然后再出栈一次,把3
赋值给min
即可。 - 通过上边的方式,我们就只需要一个栈了。当有更小的值来的时候,我们只需要把之前的最小值入栈,当前更小的值再入栈即可。当这个最小值要出栈的时候,下一个值便是之前的最小值了。
class MinStack{
int min = Integer.MAX_VALUE;
Stack<Integer> stack = new Stack<Integer>();
public void push(int x){
//当前值更小
if(x <= min){
//将之前的更小值保存
stack.push(min);
//更新最小值
min = x;
}
stack.push(x);
}
public void pop(){
//如果弹出的值是最小值,那么将下一个元素更新为最小值
if(stack.pop() == min) {
min=stack.pop();
}
}
public int top(){
return stack.peek();
}
public int getMin(){
return min;
}
}
Leetcode 946 验证栈序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
解法一:辅助栈
思路:
考虑借用一个辅助栈stack
,模拟 压入 / 弹出操作的排列。根据是否模拟成功,即可得到结果。
- 入栈操作: 按照压栈序列的顺序执行。
- 出栈操作: 每次入栈后,循环判断 “栈顶元素 == 弹出序列的当前元素” 是否成立,将符合弹出序列顺序的栈顶元素全部弹出。
public boolean validateStackSequences(int[] pushed,int[] poped){
Stack<Integer> stack = new Stack<>();
int i = 0;
for(int num : pushed){
stack.push(num);
while(!stack.isEmpty() && stack.peek() == poped[i]) { // 循环判断与出栈
stack.pop();
i++;
}
}
return stack.isEmpty();
}
解法二:不借助栈
O(1) 空间 O(n) 时间
public boolean validateStackSequences(int[] pushed, int[] popped) {
int i = 0, j = 0;
for (int e : pushed) {
pushed[i] = e;
while (i >= 0 && pushed[i] == popped[j]) {
j++;
i--;
}
i++;
}
return i == 0;
}