03删除最外层括号1021
1.Comprehend 理解题意
给出一个非空有效字符串S,考虑将其进行原语化分解。 使得:S=P_1+P_2+…+P_k,其中P_i是有效括号字符串原语。 对S进行原语化分解,删除分解中每个原语字符串的最外层括号,返回S。
➢ 有效括号字符串:例如:“( )”,“( ( ) ) ( )” 和 “( ( ) ( ( ) ) )”
➢ 原语:如果有效字符串S非空,且不能将其拆分为 S =A+B,我们称其为原语(primitive), 其中A和B都是非空有效括号字符串。
题目基本信息
- 有效括号字符串:括号必然成对出现
- 给定字符串 S 非空,但返回结果可能为空
- 原语化分解只需要进行一级
- 返回删除每个原语的最外层括号后的字符串
附加信息
- S.length <= 10000
- S[i]为"(“或”)"
解法一:暴力解法
- 先分解成字符串原语
- 再删除每部分最外层括号
- 返回各部分合并后的结果
解法二:优化解法
- 对原字符串进行原语识别
- 获取不包含最外层括号的子串
- 将各部分拼接返回
2.Choose 数据结构及算法思维选择
解法一:分解、删除再合并
- 数据结构:字符串、数组
- 算法思维:遍历、计数器、累加
解法一:暴力解法思路分析
- 定义容器存储原语子串 new ArrayList();
- 定义左括号、右括号计数器: int left = 0, right = 0;
- 遍历字符串,读取到括号时对应计数器自增
- 检查是否到达原语结尾,截取原语子串并添加 到容器中
- 遍历容器,删除最外层括号后合并成新串
解法一:边界和细节问题
- 边界问题:
遍历字符串,注意索引越界:i < S.length() 截取原语字符串时,注意起止索引:[start, end)
- 细节问题:
需要记录上一次截取原语子串之后的位置 删除原语子串的最外层括号,其实就是重新截取
解法一:代码
class Solution {
public String removeOuterParentheses(String s) {
char[] chars = s.toCharArray();
int length = chars.length;
int leftCount = 0, rightCount = 0, primitiveStart = 0;
List<String> primitives = new ArrayList<>();
for (int i = 0; i < length; i++) {
char aChar = chars[i];
if (aChar == '(') {
leftCount++;
} else if (aChar == ')') {
rightCount++;
}
if (leftCount == rightCount) {
primitives.add(s.substring(primitiveStart, i + 1));
primitiveStart = i + 1;
}
}
StringBuilder result = new StringBuilder();
for (String primitive : primitives) {
result.append(primitive.substring(1,primitive.length()-1));
}
return result.toString();
}
}
解法二:优化解法思路分析
- 定义容器存储删除外层括号后的原语子串 new StringBuilder();
- 定义左括号、右括号计数器: int left = 0, right = 0;
- 遍历字符串,读取到括号时对应计数器自增
- 检查是否到达原语结尾,截取不包含最外层的 原语子串并拼接到容器中
解法二代码
class Solution {
public String removeOuterParentheses(String s) {
char[] chars = s.toCharArray();
int length = chars.length;
StringBuilder result = new StringBuilder();
int leftCount = 0, rightCount = 0, primitiveStart = 0;
for (int i = 0; i < length; i++) {
char aChar = chars[i];
if (aChar == '(') {
leftCount++;
} else /*if (aChar == ')')*/ {
rightCount++;
}
if (leftCount == rightCount) {
result.append(s.substring(primitiveStart + 1, i));
primitiveStart = i + 1;
}
}
return result.toString();
}
}
4.Consider 思考更优解
- 剔除无效代码或优化空间消耗
- 只需要一个计数器:"(“自增,”)"自减
- 如果字符串数据只是两两配对怎么处理?
- 有没有一种支持后进先出的数据结构?
- 寻找更好的算法思维 • 借鉴其它算法
关键知识点:栈(Stack)
- 限定仅在表尾进行插入和删除操作的线性表
- 栈顶(Top):操作数据的一端,即表尾
- 栈底(Bottom):线性表的另一端,即表头
- 特点:LIFO(Last In First Out),后进先出
- 基本操作:
• 进栈:push(),在栈顶插入元素,入栈、压栈 • 出栈:pop(),从栈顶删除数据
• 判断空:isEmpty()
5.Code 最优解思路及编码实现
最优解:栈解法
-
使用数组模拟一个栈,临时存储字符,替代计数器
- push(Character) ; pop(); isEmpty()
-
遍历字符串,根据情况进行入栈/出栈操作
- 读取到左括号,左括号入栈
- 读取到右括号,左括号出栈
-
判断栈是否为空,若为空,找到了一个完整的原语
-
截取不含最外层括号的原语子串并进行拼接
最优解:边界和细节问题
- 边界问题:
遍历字符串,注意索引越界:i < S.length() 截取原语字符串时,注意起止索引:[start, end)
- 细节问题:
需要记录上一次截取原语子串之后的位置 右括号无需进栈
最优解代码
class Solution {
public String removeOuterParentheses(String s) {
StringBuilder result = new StringBuilder();
char[] chars = s.toCharArray();
int length = chars.length;
int stackCount = 0;
for (int i = 0; i < length; i++) {
char aChar = chars[i];
if (aChar == '(') {
stackCount++;
if (stackCount > 1) {
result.append(aChar);
}
} else {//aChar = ')'
stackCount--;
if (stackCount > 0) {
result.append(aChar);
}
}
}
return result.toString();
}
}
6.Change 变形延伸
题目变形
- (练习)删除最内层的括号
- (练习)用链表实现一个栈(链栈)
延伸扩展
- 逆波兰表达式求值:给定数字和运算符,根据规则计算出结果
- 子程序调用:方法依次进栈,最后调用的最先结束