标题:标签验证器
出处:591. 标签验证器
7 级
给定一个表示代码片段的字符串,你需要实现一个验证器来解析这段代码,并返回它是否合法。
合法的代码片段需要遵守以下的所有规则:
示例 1:
输入:
code
=
"
输出:
true
\texttt{true}
true
解释:
代码被包含在了闭合的标签内:
TAG_NAME
\texttt{TAG\_NAME}
TAG_NAME 是合法的,
TAG_CONTENT
\texttt{TAG\_CONTENT}
TAG_CONTENT 包含了一些字符和
cdata
\texttt{cdata}
cdata。
虽然
CDATA_CONTENT
\texttt{CDATA\_CONTENT}
CDATA_CONTENT 含有不匹配的起始标签和不合法的
TAG_NAME
\texttt{TAG\_NAME}
TAG_NAME,它应该被视为普通的文本,而不是标签。
所以
TAG_CONTENT
\texttt{TAG\_CONTENT}
TAG_CONTENT 是合法的,因此代码是合法的。返回
true
\texttt{true}
true。
示例 2:
输入:
code
=
" 示例 3: 输入:
code
=
"
"
\texttt{code = "~~~~~~"}
code = " " 这道题要求验证给定的表示代码片段的字符串
code
\textit{code}
code 是否为合法的标签。验证时需要重点关注的是起始标签、结束标签和
cdata
\text{cdata}
cdata。 由于标签可以嵌套,每个结束标签只能和最后一个出现的未匹配的起始标签匹配,匹配顺序符合栈的「后进先出」的规则,因此可以使用栈验证标签。从左到右遍历代码片段,遇到起始标签则入栈,遇到结束标签则判断是否和栈顶的起始标签匹配,如果匹配则将起始标签出栈,如果不匹配则代码片段不是有效的标签。 由于整个代码片段必须被合法的闭合标签包围,因此代码片段中的任何字符都必须属于闭合标签的一部分。如果存在一个字符在最外层闭合标签以外,则一定不是合法的标签。判断的方法是,如果当下标大于
0
0
0 且小于字符串长度时,栈为空,则说明存在一个字符在最外层闭合标签以外,返回
false
\text{false}
false。 对于其余情况,只需要判断是否遇到起始标签、结束标签和
cdata
\text{cdata}
cdata 即可。验证时,首先要检查起始标签、结束标签和
cdata
\text{cdata}
cdata 是否完整(即能找到开始标志和结束标志),然后验证内容是否合法: 对于起始标签,标签名应满足全部是大写字母且长度在
[
1
,
9
]
[1, 9]
[1,9] 范围内; 对于结束标签,标签名应和最近一个未匹配的起始标签的标签名相同,此时不需要验证标签名的合法性,因为标签名的合法性在起始标签中已经被验证; 对于
cdata
\text{cdata}
cdata,只需要符合格式即可,不需要验证内容。 具体实现方面,由于
cdata
\text{cdata}
cdata 的前
9
9
9 个字符确定,结束标签的前
2
2
2 个字符确定,开始标签的前
1
1
1 个标签确定,因此可以依次判断是否遇到
cdata
\text{cdata}
cdata、结束标签和起始标签。 不同类型的元素的验证方法如下。 对于
cdata
\text{cdata}
cdata 的判断,如果遇到连续的
9
9
9 个字符等于
"",则是
cdata
\text{cdata}
cdata 的起始位置,在起始位置之后的第一个
"]]>"
\texttt{"]]>"}
"]]>" 是
cdata
\text{cdata}
cdata 的结束位置。 对于结束标签的判断,如果遇到连续的
2
2
2 个字符等于
""
\texttt{""}
"",则是结束标签的起始位置,在起始位置之后的第一个
">"
\texttt{">"}
">" 是结束标签的结束位置。 对于起始标签的判断,如果遇到字符
"<"
\texttt{"<"}
"<",则是起始标签的起始位置,在起始位置之后的第一个
">"
\texttt{">"}
">" 是起始标签的结束位置。结束标签的起始位置,在起始位置之后的第一个
">"
\texttt{">"}
">" 是结束标签的结束位置。 除了
cdata
\text{cdata}
cdata、结束标签和起始标签以外,其余的字符均为某个标签内部的内容,因此不需要验证。 遍历结束时,所有的起始标签都应该被结束标签匹配,此时栈为空。当栈为空时返回
true
\text{true}
true,否则返回
false
\text{false}
false。 时间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n 是字符串
code
\textit{code}
code 的长度。需要遍历字符串数组一次,原始输入中的每个字符的操作时间都是
O
(
1
)
O(1)
O(1)。 空间复杂度:
O
(
n
)
O(n)
O(n),其中
n
n
n 是字符串
code
\textit{code}
code 的长度。空间复杂度主要取决于栈空间,栈内元素个数不会超过
n
n
n。
输出:
true
\texttt{true}
true
解释:
我们首先将代码分割为:
start_tag|tag_content|end_tag
\texttt{start\_tag|tag\_content|end\_tag}
start_tag|tag_content|end_tag。
start_tag
→
"
end_tag
→
"
tag_content
\texttt{tag\_content}
tag_content 也可被分割为:
text1|cdata|text2
\texttt{text1|cdata|text2}
text1|cdata|text2。
text1
→
">>
![cdata[]]
"
\texttt{text1} \rightarrow \texttt{">> ![cdata[]] "}
text1→">> ![cdata[]] "
cdata
→
"]>]]>"
\texttt{cdata} \rightarrow \texttt{"]>]]>"}
cdata→"]>]]>",其中
CDATA_CONTENT
\texttt{CDATA\_CONTENT}
CDATA_CONTENT 为
"
text2
→
"]]>>]"
\texttt{text2} \rightarrow \texttt{"]]>>]"}
text2→"]]>>]"
start_tag
\texttt{start\_tag}
start_tag 不是
"
cdata
\texttt{cdata}
cdata 不是
"]>]]>]]>"
\texttt{"]>]]>]]>"}
"]>]]>]]>" 的原因参照规则 7。
输出:
false
\texttt{false}
false
解释:不合法。如果
""
\texttt{""}
"" 是闭合的,那么
""
\texttt{""}
"" 一定是不匹配的,反之亦然。数据范围
解法
思路和算法
代码
class Solution {
public boolean isValid(String code) {
final int CDATA_START_LENGTH = 9, CDATA_END_LENGTH = 3, END_TAG_LENGTH = 2;
Deque<String> stack = new ArrayDeque<String>();
int length = code.length();
int index = 0;
while (index < length) {
if (index > 0 && stack.isEmpty()) {
return false;
}
if (index + CDATA_START_LENGTH <= length && code.substring(index, index + CDATA_START_LENGTH).equals(")) {
index += CDATA_START_LENGTH;
while (index <= length - CDATA_END_LENGTH && !code.substring(index, index + CDATA_END_LENGTH).equals("]]>")) {
index++;
}
if (index > length - CDATA_END_LENGTH) {
return false;
}
index += CDATA_END_LENGTH;
} else if (index + END_TAG_LENGTH <= length && code.substring(index, index + END_TAG_LENGTH).equals("")) {
index += END_TAG_LENGTH;
int start = index;
while (index < length && code.charAt(index) != '>') {
index++;
}
if (index >= length) {
return false;
}
String tag = code.substring(start, index);
if (stack.isEmpty() || !stack.peek().equals(tag)) {
return false;
}
stack.pop();
index++;
} else if (code.charAt(index) == '<') {
index++;
int start = index;
while (index < length && code.charAt(index) != '>') {
index++;
}
if (index >= length || index == start || index - start > 9) {
return false;
}
for (int i = start; i < index; i++) {
char c = code.charAt(i);
if (c < 'A' || c > 'Z') {
return false;
}
}
String tag = code.substring(start, index);
stack.push(tag);
index++;
} else {
index++;
}
}
return stack.isEmpty();
}
}
复杂度分析
升级targetSdkVersion至33(以及迁移至Androidx)
Dubbo错误排查
我的算法笔记 | leetCode easy题感受
向Zookeeper中注册内容以及从zookeeper中发现内容
面相分析API接口
【 C++ 】map、multimap的介绍和使用
Spring Boot 配置读取顺序 apollo 配置读取顺序
第十章 深度学习中的图像数据扩增(Data Augmentations)(工具)
数据结构和算法:分治