加载中...
591-标签验证器(Tag Validator)
发表于:2021-12-03 | 分类: 困难
字数统计: 2.5k | 阅读时长: 11分钟 | 阅读量:

原文链接: https://leetcode-cn.com/problems/tag-validator

英文原文

Given a string representing a code snippet, implement a tag validator to parse the code and return whether it is valid.

A code snippet is valid if all the following rules hold:

  1. The code must be wrapped in a valid closed tag. Otherwise, the code is invalid.
  2. A closed tag (not necessarily valid) has exactly the following format : <TAG_NAME>TAG_CONTENT</TAG_NAME>. Among them, <TAG_NAME> is the start tag, and </TAG_NAME> is the end tag. The TAG_NAME in start and end tags should be the same. A closed tag is valid if and only if the TAG_NAME and TAG_CONTENT are valid.
  3. A valid TAG_NAME only contain upper-case letters, and has length in range [1,9]. Otherwise, the TAG_NAME is invalid.
  4. A valid TAG_CONTENT may contain other valid closed tags, cdata and any characters (see note1) EXCEPT unmatched <, unmatched start and end tag, and unmatched or closed tags with invalid TAG_NAME. Otherwise, the TAG_CONTENT is invalid.
  5. A start tag is unmatched if no end tag exists with the same TAG_NAME, and vice versa. However, you also need to consider the issue of unbalanced when tags are nested.
  6. A < is unmatched if you cannot find a subsequent >. And when you find a < or </, all the subsequent characters until the next > should be parsed as TAG_NAME (not necessarily valid).
  7. The cdata has the following format : <![CDATA[CDATA_CONTENT]]>. The range of CDATA_CONTENT is defined as the characters between <![CDATA[ and the first subsequent ]]>.
  8. CDATA_CONTENT may contain any characters. The function of cdata is to forbid the validator to parse CDATA_CONTENT, so even it has some characters that can be parsed as tag (no matter valid or invalid), you should treat it as regular characters.

 

Example 1:

Input: code = "<DIV>This is the first line <![CDATA[<div>]]></DIV>"
Output: true
Explanation: 
The code is wrapped in a closed tag : <DIV> and </DIV>. 
The TAG_NAME is valid, the TAG_CONTENT consists of some characters and cdata. 
Although CDATA_CONTENT has an unmatched start tag with invalid TAG_NAME, it should be considered as plain text, not parsed as a tag.
So TAG_CONTENT is valid, and then the code is valid. Thus return true.

Example 2:

Input: code = "<DIV>>>  ![cdata[]] <![CDATA[<div>]>]]>]]>>]</DIV>"
Output: true
Explanation:
We first separate the code into : start_tag|tag_content|end_tag.
start_tag -> "<DIV>"
end_tag -> "</DIV>"
tag_content could also be separated into : text1|cdata|text2.
text1 -> ">>  ![cdata[]] "
cdata -> "<![CDATA[<div>]>]]>", where the CDATA_CONTENT is "<div>]>"
text2 -> "]]>>]"
The reason why start_tag is NOT "<DIV>>>" is because of the rule 6.
The reason why cdata is NOT "<![CDATA[<div>]>]]>]]>" is because of the rule 7.

Example 3:

Input: code = "<A>  <B> </A>   </B>"
Output: false
Explanation: Unbalanced. If "<A>" is closed, then "<B>" must be unmatched, and vice versa.

Example 4:

Input: code = "<DIV>  div tag is not closed  <DIV>"
Output: false

 

Constraints:

  • 1 <= code.length <= 500
  • code consists of English letters, digits, '<', '>', '/', '!', '[', ']', '.', and ' '.

中文题目

给定一个表示代码片段的字符串,你需要实现一个验证器来解析这段代码,并返回它是否合法。合法的代码片段需要遵守以下的所有规则:

  1. 代码必须被合法的闭合标签包围。否则,代码是无效的。
  2. 闭合标签(不一定合法)要严格符合格式:<TAG_NAME>TAG_CONTENT</TAG_NAME>。其中,<TAG_NAME>是起始标签,</TAG_NAME>是结束标签。起始和结束标签中的 TAG_NAME 应当相同。当且仅当 TAG_NAME 和 TAG_CONTENT 都是合法的,闭合标签才是合法的
  3. 合法的 TAG_NAME 仅含有大写字母,长度在范围 [1,9] 之间。否则,该 TAG_NAME 是不合法的
  4. 合法的 TAG_CONTENT 可以包含其他合法的闭合标签cdata (请参考规则7)和任意字符(注意参考规则1)除了不匹配的<、不匹配的起始和结束标签、不匹配的或带有不合法 TAG_NAME 的闭合标签。否则,TAG_CONTENT 是不合法的
  5. 一个起始标签,如果没有具有相同 TAG_NAME 的结束标签与之匹配,是不合法的。反之亦然。不过,你也需要考虑标签嵌套的问题。
  6. 一个<,如果你找不到一个后续的>与之匹配,是不合法的。并且当你找到一个<</时,所有直到下一个>的前的字符,都应当被解析为 TAG_NAME(不一定合法)。
  7. cdata 有如下格式:<![CDATA[CDATA_CONTENT]]>CDATA_CONTENT 的范围被定义成 <![CDATA[ 和后续的第一个 ]]>之间的字符。
  8. CDATA_CONTENT 可以包含任意字符。cdata 的功能是阻止验证器解析CDATA_CONTENT,所以即使其中有一些字符可以被解析为标签(无论合法还是不合法),也应该将它们视为常规字符

合法代码的例子:

输入: "<DIV>This is the first line <![CDATA[<div>]]></DIV>"

输出: True

解释: 

代码被包含在了闭合的标签内: <DIV> 和 </DIV> 。

TAG_NAME 是合法的,TAG_CONTENT 包含了一些字符和 cdata 。 

即使 CDATA_CONTENT 含有不匹配的起始标签和不合法的 TAG_NAME,它应该被视为普通的文本,而不是标签。

所以 TAG_CONTENT 是合法的,因此代码是合法的。最终返回True。


输入: "<DIV>>>  ![cdata[]] <![CDATA[<div>]>]]>]]>>]</DIV>"

输出: True

解释:

我们首先将代码分割为: start_tag|tag_content|end_tag 。

start_tag -> "<DIV>"

end_tag -> "</DIV>"

tag_content 也可被分割为: text1|cdata|text2 。

text1 -> ">>  ![cdata[]] "

cdata -> "<![CDATA[<div>]>]]>" ,其中 CDATA_CONTENT 为 "<div>]>"

text2 -> "]]>>]"


start_tag "<DIV>>>" 的原因参照规则 6 。
cdata "<![CDATA[<div>]>]]>]]>" 的原因参照规则 7 。

不合法代码的例子:

输入: "<A>  <B> </A>   </B>"
输出: False
解释: 不合法。如果 "<A>" 是闭合的,那么 "<B>" 一定是不匹配的,反之亦然。

输入: "<DIV>  div tag is not closed  <DIV>"
输出: False

输入: "<DIV>  unmatched <  </DIV>"
输出: False

输入: "<DIV> closed tags with invalid tag name  <b>123</b> </DIV>"
输出: False

输入: "<DIV> unmatched tags with invalid tag name  </1234567890> and <CDATA[[]]>  </DIV>"
输出: False

输入: "<DIV>  unmatched start tag <B>  and unmatched end tag </C>  </DIV>"
输出: False

注意:

  1. 为简明起见,你可以假设输入的代码(包括提到的任意字符)只包含数字, 字母, '<','>','/','!','[',']'' '

通过代码

官方题解

栈:

我们可以使用栈来模拟整个代码片段的解析过程。当我们发现一个起始标签时,我们把标签入栈;当我们发现一个结束标签时,我们必须保证它和栈顶的起始标签的 TAG_NAME 相匹配。在代码解析完毕之后,我们必须保证栈为空。

我们从代码的起始位置遍历整个代码片段。当我们发现 < 时,如果我们目前不在 cdata 的范围内,那么我们必须解析这个 <,即接下来一定是一个标签(起始标签或结束标签)或者一段 cdata。如果 < 后面接着的是 !,那么后面一定是一段 cdata,接下来必须匹配到 [CDATA[。在这之后,我们就可以遍历代码片段直到遇到 ]]>,表示 cdata 的结束,这中间的所有特殊符号我们都不需要解析。

如果 < 后面接着的不是 !,那么它一定是一个标签。如果是 </ 那么它是结束标签,否则是开始标签。我们继续遍历代码片段,直到遇到 > 表示标签的结束为止。此时 <</> 之间的部分就是 TAG_NAME,我们需要检查 TAG_NAME 的合法性。如果它是一个起始标签,我们会把 TAG_NAME 入栈,如果它是一个结束标签,我们需要检查 TAG_NAME 和栈顶的元素是否相同。如果不相同或者栈为空,那么这就是一个不合法的结束标签。

在代码片段遍历结束后,我们还需要检查两点:第一是栈是否为空,如果不为空,说明还有未闭合的标签;第二是代码片段是否被合法的闭合标签包围,我们需要保证在第一个起始标签被闭合后,接下来不会有任何代码,并且每个 cdata 必须在栈不为空的时候才能出现。

<1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000>

[sol1]
public class Solution { Stack < String > stack = new Stack < > (); boolean contains_tag = false; public boolean isValidTagName(String s, boolean ending) { if (s.length() < 1 || s.length() > 9) return false; for (int i = 0; i < s.length(); i++) { if (!Character.isUpperCase(s.charAt(i))) return false; } if (ending) { if (!stack.isEmpty() && stack.peek().equals(s)) stack.pop(); else return false; } else { contains_tag = true; stack.push(s); } return true; } public boolean isValidCdata(String s) { return s.indexOf("[CDATA[") == 0; } public boolean isValid(String code) { if (code.charAt(0) != '<' || code.charAt(code.length() - 1) != '>') return false; for (int i = 0; i < code.length(); i++) { boolean ending = false; int closeindex; if(stack.isEmpty() && contains_tag) return false; if (code.charAt(i) == '<') { if (!stack.isEmpty() && code.charAt(i + 1) == '!') { closeindex = code.indexOf("]]>", i + 1); if (closeindex < 0 || !isValidCdata(code.substring(i + 2, closeindex))) return false; } else { if (code.charAt(i + 1) == '/') { i++; ending = true; } closeindex = code.indexOf('>', i + 1); if (closeindex < 0 || !isValidTagName(code.substring(i + 1, closeindex), ending)) return false; } i = closeindex; } } return stack.isEmpty() && contains_tag; } }

复杂度分析

  • 时间复杂度:$O(N)$,其中 $N$ 是代码片段的长度。

  • 空间复杂度:$O(N)$。

统计信息

通过次数 提交次数 AC比率
1914 5590 34.2%

提交历史

提交时间 提交结果 执行时间 内存消耗 语言

相似题目

题目 难度
给字符串添加加粗标签 中等
上一篇:
587-安装栅栏(Erect the Fence)
下一篇:
593-有效的正方形(Valid Square)
本文目录
本文目录