Hard

题目描述

给定一个表示代码片段的字符串,实现一个标签验证器来解析代码并返回它是否有效。

如果代码片段满足以下所有规则,则它是有效的:

  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 和任何字符,除了不匹配的 <、不匹配的开始和结束标签以及具有无效 TAG_NAME 的不匹配或闭合标签。否则,TAG_CONTENT 无效。
  5. 如果不存在具有相同 TAG_NAME 的结束标签,则开始标签不匹配,反之亦然。但是,在标签嵌套时,还需要考虑不平衡问题。
  6. 如果找不到后续的 >,则 < 不匹配。当找到 <</ 时,直到下一个 > 的所有后续字符都应解析为 TAG_NAME(不一定有效)。
  7. cdata 具有以下格式:<![CDATA[CDATA_CONTENT]]>。CDATA_CONTENT 的范围定义为 <![CDATA[ 和第一个后续 ]]> 之间的字符。
  8. CDATA_CONTENT 可能包含任何字符。cdata 的功能是禁止验证器解析 CDATA_CONTENT,因此即使它有一些可以解析为标签的字符(无论有效还是无效),你都应该将其视为常规字符。

示例 1:

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

示例 2:

输入: code = "<DIV>>>  ![cdata[]] <![CDATA[<div>]>]]>]]>>]</DIV>"
输出: true

示例 3:

输入: code = "<A>  <B> </A>   </B>"
输出: false

约束:

  • 1 <= code.length <= 500
  • code 由英文字母、数字、’<’、’>’、’/’、’!’、’[’、’]’、’.’ 和 ’ ’ 组成。

解题思路

这是一个复杂的字符串解析问题,需要模拟标签验证的整个过程。

核心思路:

  1. 使用栈来跟踪标签的匹配情况
  2. 逐字符解析,识别不同类型的标签和内容
  3. 特殊处理 CDATA 部分,跳过其内容的解析

解析策略:

  • 首先检查代码是否被有效的闭合标签包围
  • 使用栈记录遇到的开始标签
  • 遇到结束标签时,检查是否与栈顶的开始标签匹配
  • 对于 CDATA,直接跳过到 ]]> 结束位置
  • 验证标签名是否符合规则(1-9个大写字母)

关键难点:

  1. 正确解析各种标签格式
  2. 处理嵌套标签的平衡性
  3. CDATA 内容的特殊处理
  4. 边界情况的处理(如不完整的标签)

算法流程:

  1. 移除最外层标签,验证其有效性
  2. 使用栈和索引遍历内容
  3. 遇到 < 时判断是开始标签、结束标签还是 CDATA
  4. 验证标签名的有效性
  5. 确保所有标签都正确匹配

这种方法能够准确处理所有边界情况,包括嵌套标签、CDATA 和各种非法格式。

代码实现

class Solution {
public:
    bool isValid(string code) {
        stack<string> st;
        int i = 0;
        
        while (i < code.length()) {
            if (i == 0 || !st.empty()) {
                if (code[i] == '<') {
                    if (i + 8 < code.length() && code.substr(i, 9) == "<![CDATA[") {
                        // Handle CDATA
                        int j = i + 9;
                        while (j + 2 < code.length() && code.substr(j, 3) != "]]>") {
                            j++;
                        }
                        if (j + 2 >= code.length()) return false;
                        i = j + 3;
                    } else {
                        // Handle tag
                        int j = i + 1;
                        bool isClosing = false;
                        if (j < code.length() && code[j] == '/') {
                            isClosing = true;
                            j++;
                        }
                        
                        int tagStart = j;
                        while (j < code.length() && code[j] != '>') {
                            if (!isalpha(code[j]) || !isupper(code[j])) return false;
                            j++;
                        }
                        if (j >= code.length()) return false;
                        
                        string tagName = code.substr(tagStart, j - tagStart);
                        if (tagName.length() == 0 || tagName.length() > 9) return false;
                        
                        if (isClosing) {
                            if (st.empty() || st.top() != tagName) return false;
                            st.pop();
                        } else {
                            st.push(tagName);
                        }
                        i = j + 1;
                    }
                } else {
                    i++;
                }
            } else {
                return false;
            }
        }
        
        return st.empty();
    }
};
class Solution:
    def isValid(self, code: str) -> bool:
        stack = []
        i = 0
        
        while i < len(code):
            if i == 0 or stack:
                if code[i] == '<':
                    if i + 8 < len(code) and code[i:i+9] == "<![CDATA[":
                        # Handle CDATA
                        j = i + 9
                        while j + 2 < len(code) and code[j:j+3] != "]]>":
                            j += 1
                        if j + 2 >= len(code):
                            return False
                        i = j + 3
                    else:
                        # Handle tag
                        j = i + 1
                        is_closing = False
                        if j < len(code) and code[j] == '/':
                            is_closing = True
                            j += 1
                        
                        tag_start = j
                        while j < len(code) and code[j] != '>':
                            if not code[j].isupper():
                                return False
                            j += 1
                        if j >= len(code):
                            return False
                        
                        tag_name = code[tag_start:j]
                        if len(tag_name) == 0 or len(tag_name) > 9:
                            return False
                        
                        if is_closing:
                            if not stack or stack[-1] != tag_name:
                                return False
                            stack.pop()
                        else:
                            stack.append(tag_name)
                        i = j + 1
                else:
                    i += 1
            else:
                return False
        
        return len(stack) == 0
public class Solution {
    public bool IsValid(string code) {
        var stack = new Stack<string>();
        int i = 0;
        
        while (i < code.Length) {
            if (i == 0 || stack.Count > 0) {
                if (code[i] == '<') {
                    if (i + 8 < code.Length && code.Substring(i, 9) == "<![CDATA[") {
                        // Handle CDATA
                        int j = i + 9;
                        while (j + 2 < code.Length && code.Substring(j, 3) != "]]>") {
                            j++;
                        }
                        if (j + 2 >= code.Length) return false;
                        i = j + 3;
                    } else {
                        // Handle tag
                        int j = i + 1;
                        bool isClosing = false;
                        if (j < code.Length && code[j] == '/') {
                            isClosing = true;
                            j++;
                        }
                        
                        int tagStart = j;
                        while (j < code.Length && code[j] != '>') {
                            if (!char.IsUpper(code[j])) return false;
                            j++;
                        }
                        if (j >= code.Length) return false;
                        
                        string tagName = code.Substring(tagStart, j - tagStart);
                        if (tagName.Length == 0 || tagName.Length > 9) return false;
                        
                        if (isClosing) {
                            if (stack.Count == 0 || stack.Peek() != tagName) return false;
                            stack.Pop();
                        } else {
                            stack.Push(tagName);
                        }
                        i = j + 1;
                    }
                } else {
                    i++;
                }
            } else {
                return false;
            }
        }
        
        return stack.Count == 0;
    }
}
var isValid = function(code) {
    const stack = [];
    let i = 0;
    
    while (i < code.length) {
        if (i == 0 || stack.length > 0) {
            if (code.startsWith('<![CDATA[', i)) {
                let j = code.indexOf(']]>', i + 9);
                if (j == -1) return false;
                i = j + 3;
            } else if (code.startsWith('</', i)) {
                let j = i + 2;
                while (j < code.length && code[j] != '>') j++;
                if (j == code.length) return false;
                let tagName = code.substring(i + 2, j);
                if (!isValidTagName(tagName) || stack.length == 0 || stack.pop() != tagName) {
                    return false;
                }
                i = j + 1;
            } else if (code[i] == '<') {
                let j = i + 1;
                while (j < code.length && code[j] != '>') j++;
                if (j == code.length) return false;
                let tagName = code.substring(i + 1, j);
                if (!isValidTagName(tagName)) return false;
                stack.push(tagName);
                i = j + 1;
            } else {
                i++;
            }
        } else {
            return false;
        }
    }
    
    return stack.length == 0;
};

function isValidTagName(tagName) {
    if (tagName.length < 1 || tagName.length > 9) return false;
    for (let c of tagName) {
        if (c < 'A' || c > 'Z') return false;
    }
    return true;
}

复杂度分析

复杂度类型说明
时间复杂度O(n)需要遍历整个字符串一次,其中 n 是字符串长度
空间复杂度O(n)在最坏情况下,栈可能存储所有的开始标签

相关题目