当前位置: 首页 > article >正文

LeetCode题练习与总结:标签验证器--591

一、题目描述

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

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

二、解题思路

  • 首先,我们需要定义一个函数来检查一个字符串是否是合法的标签名。这个函数应该检查字符串是否只包含大写字母,并且长度在1到9之间。

  • 我们需要一个辅助函数来解析字符串并找到下一个标签的开始位置。这个函数应该返回标签的类型(起始标签、结束标签或CDATA),标签名,以及下一个标签的开始位置。

  • 使用一个栈来管理标签。当我们遇到一个起始标签时,将其推入栈中;当我们遇到一个结束标签时,检查栈顶元素是否与结束标签匹配,如果匹配则弹出栈顶元素。

  • 解析字符串时,我们需要处理以下几种情况:

    • 如果遇到<,我们需要判断是起始标签、结束标签还是CDATA。
    • 如果是起始标签或结束标签,我们需要检查标签名是否合法,并相应地处理栈。
    • 如果是CDATA,我们需要找到CDATA的结束位置,并跳过其中的内容。
    • 如果遇到其他字符,我们需要确保它们不是不匹配的<
  • 在解析结束后,如果栈为空,则代码是合法的;否则,它是非法的。

三、具体代码

import java.util.*;

public class Solution {
    public boolean isValid(String code) {
        Stack<String> stack = new Stack<>();
        int i = 0;
        while (i < code.length()) {
            if (code.charAt(i) == '<') {
                if (i == code.length() - 1) return false; // '<' is the last character
                if (code.charAt(i + 1) == '/') { // End tag
                    int j = code.indexOf('>', i);
                    if (j == -1) return false; // No matching '>'
                    String tagName = code.substring(i + 2, j);
                    if (!isValidTagName(tagName) || stack.isEmpty() || !stack.pop().equals(tagName)) {
                        return false;
                    }
                    i = j + 1;
                } else if (code.charAt(i + 1) == '!') { // CDATA
                    if (!stack.isEmpty() && code.startsWith("<![CDATA[", i)) {
                        int j = code.indexOf("]]>", i);
                        if (j == -1) return false; // No matching ']]>'
                        i = j + 3;
                    } else {
                        return false;
                    }
                } else { // Start tag
                    int j = code.indexOf('>', i);
                    if (j == -1) return false; // No matching '>'
                    String tagName = code.substring(i + 1, j);
                    if (!isValidTagName(tagName)) return false;
                    stack.push(tagName);
                    i = j + 1;
                }
            } else {
                i++;
            }
        }
        return stack.isEmpty();
    }

    private boolean isValidTagName(String tagName) {
        return tagName.matches("[A-Z]{1,9}");
    }
}

这段代码定义了一个Solution类,其中包含isValid方法用于检查代码是否合法,以及一个辅助方法isValidTagName用于检查标签名是否合法。代码逻辑按照上述解题思路实现。

四、时间复杂度和空间复杂度

1. 时间复杂度
  • 我们定义code.length()n,这是输入字符串的长度。

  • 我们有一个主循环,它遍历字符串code的每个字符。这个循环最多执行n次。

  • 在每次循环中,我们可能会执行以下操作:

    • 检查是否是起始标签、结束标签或CDATA,这涉及到字符串比较,其时间复杂度为O(1)。
    • 查找下一个>字符的位置,使用indexOf方法,其时间复杂度为O(n)。
    • 检查标签名是否合法,使用正则表达式匹配,其时间复杂度为O(1),因为标签名长度限制在1到9之间。
    • 向栈中推入或弹出元素,其时间复杂度为O(1)。
  • 由于indexOf方法在每次遇到<字符时都会被调用,并且在最坏情况下,它可能需要遍历整个字符串,因此,整个算法的时间复杂度主要取决于indexOf方法的调用次数和每次调用的成本。

  • 在最坏情况下,indexOf方法可能在每次循环迭代中被调用一次,总共有n次迭代,因此时间复杂度为O(n^2)。

2. 空间复杂度
  • 我们使用了一个栈stack来存储标签名。

  • 在最坏的情况下,如果所有标签都是嵌套的,那么栈的大小可能达到n/2(假设每个标签都是成对的,并且每个标签长度为2,这是最紧凑的情况)。

  • 因此,空间复杂度为O(n),其中n是输入字符串的长度。

五、总结知识点

  • Java 类的定义

    • public class Solution 定义了一个公共类 Solution
  • Java 方法的定义

    • public boolean isValid(String code) 定义了一个公共方法 isValid,它接受一个字符串参数并返回一个布尔值。
  • Java 栈的使用

    • Stack<String> stack = new Stack<>(); 创建了一个字符串类型的栈,用于管理标签。
  • 字符串操作

    • 使用 charAt(int index) 方法来获取字符串中特定位置的字符。
    • 使用 indexOf(char ch) 方法来查找字符在字符串中的位置。
    • 使用 substring(int beginIndex, int endIndex) 方法来获取字符串的子串。
  • 循环和条件语句

    • 使用 while 循环来遍历字符串。
    • 使用 if-else 语句来处理不同类型的标签(起始标签、结束标签、CDATA)。
  • 正则表达式

    • 使用 matches(String regex) 方法来检查字符串是否符合给定的正则表达式模式。
  • 异常处理

    • 检查字符串是否以特定的子串开始,例如 startsWith(String prefix)
    • 检查字符串是否在特定位置结束,例如检查是否没有匹配的结束标签 >
  • 栈操作

    • 使用 push(E element) 方法将元素推入栈。
    • 使用 pop() 方法从栈中移除并返回栈顶元素。
    • 使用 isEmpty() 方法检查栈是否为空。
  • 字符串与字符的比较

    • 使用 == 运算符来比较字符是否相等。
  • 错误处理

    • 返回 false 来处理各种非法情况,例如不匹配的标签、不合法的标签名、字符串结束前没有闭合的标签等。

以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。


http://www.kler.cn/a/523152.html

相关文章:

  • (开源)基于Django+Yolov8+Tensorflow的智能鸟类识别平台
  • 海外问卷调查渠道查如何设置:最佳实践+示例
  • 深入探索C++17的std::any:类型擦除与泛型编程的利器
  • 《哈佛家训》
  • ESP32 I2S音频总线学习笔记(二):I2S读取INMP441音频数据
  • 【MQ】如何保证消息队列的高可用?
  • 手写一个深克隆!!
  • LeetCode:70. 爬楼梯
  • 排序算法- H指数
  • 【C语言分支与循环结构详解】
  • 如何下载SQLServer
  • fprintf(‘parametric_vector:\n‘); disp(parametric_vector);
  • 损失函数 Loss Function
  • 【番外篇】鸿蒙扫雷天纪:运混沌灵智勘破雷劫天局
  • 深入探索 HTML5 拖拽效果 API:打造流畅交互体验
  • 27.收益的定义是什么?
  • 2024年终总结——今年是蜕变的一年
  • 砥砺奋进,展望新程0114
  • Webpack 打包性能优化全解
  • 2025数学建模美赛|D题成品论文
  • 第四节 MATLAB变量
  • BurpSuite--暴力破解
  • Redis 集合(Set)
  • java多线程学习笔记
  • golang中的包管理-上--简介
  • 视频拼接,拼接时长版本