LeetCode 591: Tag Validator Solution in Python – A Step-by-Step Guide

Imagine you’re tasked with checking if a snippet of code—like "This is a test"—follows the rules of a simple HTML-like tagging system, ensuring tags open and close correctly, such as validating it as true, while rejecting "unclosed" as false. That’s the intriguing challenge of LeetCode 591: Tag Validator, a hard-level problem that’s a fantastic way to practice string parsing in Python. We’ll explore two solutions: the Best Solution, a stack-based parsing approach that’s efficient and clear, and an Alternative Solution, a recursive parsing method that’s thorough but more complex. With detailed examples, clear code, and a friendly tone—especially for the stack approach—this guide will help you validate those tags, whether you’re new to coding or leveling up. Let’s parse that code and start validating!

What Is LeetCode 591: Tag Validator?

In LeetCode 591: Tag Validator, you’re given a string code representing a snippet of HTML-like markup, and your task is to return True if it’s a valid tag sequence and False otherwise. A valid sequence must follow these rules: tags are uppercase, 1-9 characters long (e.g., ,

), must close in matching pairs (e.g., ), and content between tags must be valid (plain text, CDATA, or nested tags). For example, code = "
This is a test
"
is valid, but "
unclosed"
is not. This problem builds on LeetCode 20: Valid Parentheses for stack-based parsing but extends to complex tag structures with content and CDATA sections.

Problem Statement

  • Input: code (str)—HTML-like tag sequence.
  • Output: Boolean—True if valid, False otherwise.
  • Rules:
    • Tags: <tag_name></tag_name> where TAG_NAME is 1-9 uppercase letters.
    • Closing: <tag_name></tag_name> must pair with.
    • Content: Plain text, CDATA (...), or nested valid tags.
    • No < or > within plain text unless part of tags/CDATA.

Constraints

  • 1 <= code.length <= 5 * 10⁴
  • code contains uppercase letters, lowercase letters, digits, <, >, /, [, ], !, and text.

Examples

  • Input: code = "
    This is a test
    "
    • Output: True
    • Valid:
      opens, closes with , text content allowed.
  • Input: code = "
    unclosed"
    • Output: False
    • Missing closing tag .
  • Input: code = "
    <test>
    "
    • Output: True
    • CDATA allows < and > inside, valid with matching tags.

Understanding the Problem: Validating Tags

To solve LeetCode 591: Tag Validator in Python, we need to check if a string follows strict tag rules—matching open/close pairs, valid tag names, and proper content—handling up to 5 * 10⁴ characters efficiently. A brute-force approach manually scanning for each tag could work but would be slow and error-prone (O(n²)). Instead, a stack-based parsing method processes the string in O(n) time by tracking opening tags and validating closures, using a pointer to parse linearly. We’ll explore:

  • Best Solution (Stack-Based Parsing): O(n) time, O(n) space—fast and optimal (n = string length).
  • Alternative Solution (Recursive Parsing): O(n) time, O(n) space—thorough but complex.

Let’s dive into the stack solution with a friendly breakdown!

Best Solution: Stack-Based Parsing

Why Stack Wins

The stack-based parsing solution is the best for LeetCode 591 because it validates the tag sequence in O(n) time and O(n) space by using a stack to track opening tags, parsing the string character-by-character with a pointer, and efficiently handling nested tags and CDATA sections in a single pass. It’s like reading a book, keeping a bookmark stack of where each chapter starts, and checking they close properly—all in a swift, organized read!

How It Works

Think of this as a tag-checking librarian:

  • Step 1: Initialize Stack and Pointer:
    • Stack for opening tags, pointer i at 0.
  • Step 2: Parse String:
    • If < found:
      • Check for closing tag , validate against stack top.
      • Check for CDATA , skip to end ]]>.
      • Otherwise, parse opening tag, push to stack.
    • Move pointer past tag/content.
  • Step 3: Validate Tag Name:
    • 1-9 uppercase letters between < and >.
  • Step 4: Check Completion:
    • Stack empty at end = valid.
  • Why It Works:
    • Stack ensures matching tags in correct order.
    • Linear scan handles complexity efficiently.

It’s like a tag-validation maestro!

Step-by-Step Example

Example: code = "
This is a test
"

  • Init: Stack = [], i = 0.
  • Step 1: i = 0,
    :
    • Valid tag "DIV" (3 uppercase), push "DIV", Stack = ["DIV"], i = 5.
  • Step 2: i = 5, "This is a test", skip text, i = 19.
  • Step 3: i = 19, :
    • Closing tag "DIV", pop Stack top "DIV", match, Stack = [], i = 25.
  • Step 4: End, Stack empty, valid.
  • Result: True.

Example: code = "
<![CDATA[<test>]]></test>
"

  • Step 1: i = 0,
    :
    • Push "DIV", Stack = ["DIV"], i = 5.
  • Step 2: i = 5, <test>:
    • CDATA, skip to ]]>, i = 22.
  • Step 3: i = 22, :
    • Pop "DIV", match, Stack = [], i = 28.
  • Step 4: End, Stack empty, valid.
  • Result: True.

Code with Detailed Line-by-Line Explanation

Here’s the Python code, explained for beginners:

class Solution:
    def isValid(self, code: str) -> bool:
        stack = []
        i = 0
        n = len(code)

        while i < n:
            if code[i] != '<':
                # Must be within tags or CDATA
                if not stack:
                    return False
                i += 1
                continue

            # Step 1: Handle closing tag
            if i + 1 < n and code[i + 1] == '/':
                if not stack:
                    return False
                j = code.find('>', i)
                if j == -1:
                    return False
                tag = code[i + 2:j]
                if not self._is_valid_tag(tag) or stack[-1] != tag:
                    return False
                stack.pop()
                i = j + 1
                # Must have processed entire string if stack empty
                if not stack and i < n:
                    return False
                continue

            # Step 2: Handle CDATA
            if i + 8 < n and code[i:i + 9] == '<![CDATA[':
                if not stack:
                    return False
                j = code.find(']]>', i)
                if j == -1:
                    return False
                i = j + 3
                continue

            # Step 3: Handle opening tag
            j = code.find('>', i)
            if j == -1:
                return False
            tag = code[i + 1:j]
            if not self._is_valid_tag(tag):
                return False
            stack.append(tag)
            i = j + 1

        # Step 4: Check if all tags closed
        return len(stack) == 0

    def _is_valid_tag(self, tag: str) -> bool:
        # Tag must be 1-9 uppercase letters
        return 1 <= len(tag) <= 9 and all(c.isupper() for c in tag)
  • Lines 3-6: Initialize stack and pointer.
  • Lines 8-12: Skip non-tag characters, invalid if outside tags.
  • Lines 15-27: Closing tag:
    • Check , match with stack top, pop if valid.
  • Lines 30-37: CDATA:
    • Skip to ]]>, invalid if outside tags.
  • Lines 40-47: Opening tag:
    • Parse <tag></tag>, validate, push to stack.
  • Line 50: Return True if stack empty (all tags closed).
  • Lines 53-55: Helper: Check tag is 1-9 uppercase letters.
  • Time Complexity: O(n)—single pass through string.
  • Space Complexity: O(n)—stack for nested tags.

It’s like a tag-parsing wizard!

Alternative Solution: Recursive Parsing

Why an Alternative Approach?

The recursive parsing approach breaks the string into segments, validating each recursively, running in O(n) time and O(n) space due to recursion depth. It’s thorough but more complex, making it a good alternative for understanding or when stack space isn’t a concern.

How It Works

Picture this as a recursive tag explorer:

  • Step 1: Define recursive function with start index.
  • Step 2: Base case: Empty or no tags left.
  • Step 3: Find tags, validate, recurse on content.
  • Step 4: Return validity.

It’s like a recursive tag checker!

Step-by-Step Example

Example: code = "text"

  • Step 1: Start at 0:
    • : Valid, recurse on "text" at 3.
    • "text": No tags, valid content.
    • : Matches "A", valid.
  • Step 2: Return True.
  • Result: True.

Code for Recursive Approach

class Solution:
    def isValid(self, code: str) -> bool:
        def parse(i: int, n: int) -> Tuple[bool, int]:
            if i >= n:
                return True, i
            if code[i] != '<':
                return False, i

            j = code.find('>', i)
            if j == -1 or not self._is_valid_tag(code[i + 1:j]):
                return False, i

            tag = code[i + 1:j]
            i = j + 1

            while i < n and code[i] != '<':
                i += 1
            if i >= n:
                return False, i

            if code[i:i + 2] == '</':
                k = code.find('>', i)
                if k == -1 or code[i + 2:k] != tag:
                    return False, i
                return True, k + 1

            return parse(i, n)

        def _is_valid_tag(self, tag: str) -> bool:
            return 1 <= len(tag) <= 9 and all(c.isupper() for c in tag)

        valid, end = parse(0, len(code))
        return valid and end == len(code)
  • Lines 4-24: Recursive parse:
    • Validate opening tag, recurse on content, match closing tag.
  • Lines 27-28: Tag validation helper.
  • Line 31: Check full string processed.
  • Time Complexity: O(n)—linear with recursion.
  • Space Complexity: O(n)—recursion stack.

It’s a recursive tag validator!

Comparing the Two Solutions

  • Stack (Best):
    • Pros: O(n), O(n), clear and efficient.
    • Cons: Stack space for nesting.
  • Recursive (Alternative):
    • Pros: O(n), O(n), thorough.
    • Cons: More complex logic.

Stack wins for clarity!

Additional Examples and Edge Cases

  • Empty string: False.
  • Single tag: Valid if closed.
  • Nested tags: Must match.

Stack handles them all!

Complexity Recap

  • Stack: Time O(n), Space O(n).
  • Recursive: Time O(n), Space O(n).

Stack’s the efficiency champ!

Key Takeaways

  • Stack: Parsing finesse—learn at Python Basics!
  • Recursive: Deep dive.
  • Tags: Validation is fun.
  • Python: Stack or recurse, your pick!

Final Thoughts: Validate Those Tags!

LeetCode 591: Tag Validator in Python is a parsing challenge. Stack-based parsing is your fast track, while recursive parsing offers a thorough twist. Want more? Try LeetCode 20: Valid Parentheses or LeetCode 22: Generate Parentheses. Ready to parse? Head to Solve LeetCode 591 on LeetCode and validate those tags today!