# 「力扣」第 1111 题:有效括号的嵌套深度(中等)
# 题目描述
有效括号字符串 定义:对于每个左括号,都能找到与之对应的右括号,反之亦然。详情参见题末「有效括号字符串」部分。
嵌套深度 depth
定义:即有效括号字符串嵌套的层数,depth(A)
表示有效括号字符串 A
的嵌套深度。详情参见题末「嵌套深度」部分。
有效括号字符串类型与对应的嵌套深度计算方法如下图所示:
给你一个「有效括号字符串」 seq
,请你将其分成两个不相交的有效括号字符串,A
和 B
,并使这两个字符串的深度最小。
- 不相交:每个
seq[i]
只能分给A
和B
二者中的一个,不能既属于A
也属于B
。 A
或B
中的元素在原字符串中可以不连续。A.length + B.length = seq.length
- 深度最小:
max(depth(A), depth(B))
的可能取值最小。
划分方案用一个长度为 seq.length
的答案数组 answer
表示,编码规则如下:
answer[i] = 0
,seq[i]
分给A
。answer[i] = 1
,seq[i]
分给B
。
如果存在多个满足要求的答案,只需返回其中任意 一个 即可。
示例 1:
输入:seq = "(()())"
输出:[0,1,1,1,1,0]
示例 2:
输入:seq = "()(())()"
输出:[0,0,0,1,1,0,1,1]
解释:本示例答案不唯一。
按此输出 A = "()()", B = "()()", max(depth(A), depth(B)) = 1,它们的深度最小。
像 [1,1,1,0,0,1,1,1],也是正确结果,其中 A = "()()()", B = "()", max(depth(A), depth(B)) = 1 。
提示:
1 < seq.size <= 10000
有效括号字符串:
仅由 "(" 和 ")" 构成的字符串,对于每个左括号,都能找到与之对应的右括号,反之亦然。
下述几种情况同样属于有效括号字符串:
1. 空字符串
2. 连接,可以记作 AB(A 与 B 连接),其中 A 和 B 都是有效括号字符串
3. 嵌套,可以记作 (A),其中 A 是有效括号字符串
嵌套深度:
类似地,我们可以定义任意有效括号字符串 s 的 嵌套深度 depth(S):
1. s 为空时,depth("") = 0
2. s 为 A 与 B 连接时,depth(A + B) = max(depth(A), depth(B)),其中 A 和 B 都是有效括号字符串
3. s 为嵌套情况,depth("(" + A + ")") = 1 + depth(A),其中 A 是有效括号字符串
例如:"","()()",和 "()(()())" 都是有效括号字符串,嵌套深度分别为 0,1,2,而 ")(" 和 "(()" 都不是有效括号字符串。
# 分析题意
1、首先需要弄清楚「有效括号」的意思。
- 如果做过「力扣」第 20 题:有效的括号 (opens new window),就不难明白有效括号的含义:就像我们写代码一样,
{
与}
(本题是(
与)
)需要成对出现,才叫匹配; - 由于匹配操作符合「后进先出」的特点:遇到进来的右括号
)
,它匹配后进来的左括号(
,它相对于之前扫描到的左括号(
先完成匹配,这种匹配工作需要使用的数据结构是「栈」; - 在完成括号匹配的过程中,只需要把遍历到的左括号进栈,在遍历到右括号的时候,左括号出栈,这个思路是经典且重要的。
2、看题目:
连接,可以记作
AB
(A
与B
连接),其中A
和B
都是有效括号字符串。
说明连在一起的时候,以「有效括号字符串」为最小单位。例如:A = ")("
与 B = "()"
就不能连接在一起,因为 A
不是有效括号字符串。
3、看题目:
嵌套,可以记作
(A)
,其中 A 是有效括号字符串;s
为A
与B
连接时,depth(A + B) = max(depth(A), depth(B))
,其中A
和B
都是有效括号字符串; 例如:""
,"()()"
,和"()(()())"
都是有效括号字符串,嵌套深度分别为0
,1
,2
,而")("
和"(()"
都不是有效括号字符串。
从这些信息中,可以知道:
「嵌套」才会产生「深度」,所以题目的定义是嵌套深度,极端例子 1:
()()()()
,这个字符串的「嵌套深度」为。极端例子 2: (((())))
这个字符串的「嵌套深度」为。而这个深度恰好与在匹配过程中,栈的最大高度是一致的; 在「有效括号字符串」连接的时候,
depth(A + B) = max(depth(A), depth(B))
的意思是大吃小,即在两个「有效括号字符串」连接的时候,整体深度取决于部分「有效括号字符串」的深度,这一点特别像在二叉树中的计算二叉树的高度;其实仔细观察就会发现,这个「嵌套深度」就是输入字符串,使用栈完成括号匹配,栈中最多连续出现的左括号
(
的个数。而题目要求我们把输入的整体有效字符串做一个重组,要求是只拆成两个部分
A
和B
,每个字符要么分到A
要么分到B
,分到A
标记为0
,分到B
标记为1
。而每个部分的字符又要保持在输入字符串中的顺序不变。因此输出是一个与原始字符串等长的整数数组,这个整数数组里只有0
和1
。
例如示例 1:
输入:seq = "(()())"
,嵌套深度为 2,最多连续出现了 2 个左括号。现在要拆分以后再重组,其实思路就有了,把这两个连续出现的左括号分到不同组即可。
输出:[0, 1, 1, 1, 1, 0]
对应了输入字符串的每个字符对应的组号,0
分到 A
组,1
分到 B
组。
标记为 0
的组的「嵌套深度」是 1;标记为 1 的组的「嵌套深度」是 1,因此总的「嵌套深度」就是 1。
从这个示例中我们知道,重组以后的嵌套深度是原始嵌套深度的一半(不能整除的时候上取整)。
- 如果原始嵌套深度是偶数,例如 6,重组的嵌套深度 = 3;
- 如果原始嵌套深度是奇数,例如 7,重组的嵌套深度 = 4。
示例 2:
说明:结果不唯一,但一定要保证连续的左括号 ((
不被分在同一组,具体来说,就是完成「括号匹配」问题使用的栈里,同时出现的左括号,不能在同一个分组里,这样就不会增加嵌套的深度。
# 思路分析
- 根据
depth(A + B) = max(depth(A), depth(B))
这个定义,整体的「嵌套深度」取决于子序列的「嵌套深度」的最大者; - 要使得
max(depth(A), depth(B))
的可能取值最小,分析示例的时候提到,这很像一棵二叉树,要使得二叉树的深度最小,那么就需要该二叉树平衡,一个可行的做法是:把栈中连续出现的左括号(
根据奇偶性分到不同的组,右括号随与之匹配左括号的组号; - 如果出现
()
这种子序列,即左括号后面连着出现了右括号,其实分在那一组都是没有关系的,因为它的存在不会使得「嵌套深度」更深。
置顶的评论总结了解决这道题的核心思想,大家可以看一下。
参考代码:
public class Solution {
public int[] maxDepthAfterSplit(String seq) {
int len = seq.length();
int[] res = new int[len];
// 嵌套深度,栈的当前高度
int depth = 0;
// 在 Java 里,seq.charAt(i) 函数会做下标越界检查,
// 因此先转换成字符数组是常见的做法
char[] charArray = seq.toCharArray();
for (int i = 0; i < len; i++) {
// 遍历到左括号,连续括号个数加 1,
if (charArray[i] == '(') {
depth++;
// % 2 也可以写成 & 1,为了保证语义清楚,写 % 2
res[i] = depth % 2;
} else {
// 遍历到右括号,与当前栈顶左括号分在一组,因此先取模,再 --
// 这一步希望大家多体会,很有意思
res[i] = depth % 2;
depth--;
}
}
return res;
}
}
大家还可以在「力扣」上搜索一些「括号匹配」的问题来做,在题库里搜索关键字「括号」,相信是非常有意思的问题。
今天是 4 月 1 日,官方选择第 1111 号问题,是不是很有意思呢。今天在题库里搜 reverse 或者 mirror 的题,试试看。
另外这道题的标签是「贪心」和「二分」。
- 「二分」应该是标错了,一分为二不能叫「二分」;
- 「贪心」个人觉得用只看局部,得到整体最优,这样解释感觉有点牵强。
作者:liweiwei1419 链接:https://suanfa8.com/stack/solutions-1/1111-maximum-nesting-depth-of-two-valid-parentheses-strings 来源:算法吧 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。