135 lines
4.8 KiB
YAML
135 lines
4.8 KiB
YAML
title: Valid Parentheses
|
|
slug: valid-parentheses
|
|
difficulty: easy
|
|
leetcode_id: 20
|
|
leetcode_url: https://leetcode.com/problems/valid-parentheses/
|
|
categories:
|
|
- strings
|
|
- stack
|
|
patterns:
|
|
- monotonic-stack
|
|
|
|
description: |
|
|
Given a string `s` containing just the characters `'('`, `')'`, `'{'`, `'}'`, `'['` and `']'`, determine if the input string is valid.
|
|
|
|
An input string is valid if:
|
|
1. Open brackets must be closed by the same type of brackets.
|
|
2. Open brackets must be closed in the correct order.
|
|
3. Every close bracket has a corresponding open bracket of the same type.
|
|
|
|
constraints: |
|
|
- 1 <= s.length <= 10^4
|
|
- s consists of parentheses only '()[]{}'
|
|
|
|
examples:
|
|
- input: 's = "()"'
|
|
output: "true"
|
|
explanation: "Single pair of matching parentheses."
|
|
- input: 's = "()[]{}"'
|
|
output: "true"
|
|
explanation: "Three separate valid pairs."
|
|
- input: 's = "(]"'
|
|
output: "false"
|
|
explanation: "Mismatched bracket types."
|
|
- input: 's = "([)]"'
|
|
output: "false"
|
|
explanation: "Incorrect nesting order."
|
|
|
|
explanation:
|
|
approach: |
|
|
1. Create a mapping of closing brackets to their opening counterparts
|
|
2. Initialize an empty stack to track opening brackets
|
|
3. Iterate through each character in the string:
|
|
- If it's an opening bracket, push it onto the stack
|
|
- If it's a closing bracket, check if the stack is empty (invalid) or if the top
|
|
of the stack matches the corresponding opening bracket
|
|
4. After processing all characters, the stack should be empty for a valid string
|
|
|
|
intuition: |
|
|
The key insight is that brackets must be closed in LIFO (Last-In-First-Out) order.
|
|
The most recently opened bracket must be closed first, which is exactly what a stack does.
|
|
|
|
When we encounter a closing bracket, the most recent unclosed opening bracket (top of stack)
|
|
must match it. If they don't match, or if there's no opening bracket to match, the string
|
|
is invalid.
|
|
|
|
Think of it like nested function calls — the innermost function must return before the
|
|
outer one can.
|
|
|
|
common_pitfalls:
|
|
- title: Forgetting to check empty stack
|
|
description: |
|
|
When encountering a closing bracket, you must first check if the stack is empty.
|
|
If it is, there's no matching opening bracket.
|
|
wrong_approach: "Directly checking stack[-1] without empty check"
|
|
correct_approach: "Check if stack is empty before accessing stack[-1]"
|
|
|
|
- title: Not checking if stack is empty at the end
|
|
description: |
|
|
After processing all characters, leftover opening brackets in the stack mean
|
|
they were never closed. Return stack is empty, not just True.
|
|
wrong_approach: "return True after the loop"
|
|
correct_approach: "return len(stack) == 0"
|
|
|
|
- title: Confusing bracket mapping direction
|
|
description: |
|
|
Map closing brackets to opening brackets (not vice versa) because we encounter
|
|
closing brackets when we need to check for a match.
|
|
|
|
key_takeaways:
|
|
- Stacks are ideal for matching nested structures
|
|
- LIFO order matches the nesting requirement of brackets
|
|
- Always check edge cases (empty string, only opening, only closing)
|
|
- This pattern extends to validating HTML tags, code blocks, etc.
|
|
|
|
time_complexity: "O(n)"
|
|
space_complexity: "O(n)"
|
|
complexity_explanation: |
|
|
Time: We process each character exactly once.
|
|
Space: In the worst case (all opening brackets), the stack holds n/2 elements.
|
|
|
|
solutions:
|
|
- approach_name: Stack (Optimal)
|
|
is_optimal: true
|
|
code: |
|
|
def is_valid(s: str) -> bool:
|
|
stack = []
|
|
mapping = {')': '(', '}': '{', ']': '['}
|
|
|
|
for char in s:
|
|
if char in mapping:
|
|
# Closing bracket
|
|
if not stack or stack[-1] != mapping[char]:
|
|
return False
|
|
stack.pop()
|
|
else:
|
|
# Opening bracket
|
|
stack.append(char)
|
|
|
|
return len(stack) == 0
|
|
explanation: |
|
|
Use a stack to track opening brackets. For each closing bracket,
|
|
verify it matches the most recent opening bracket.
|
|
|
|
- approach_name: Stack with Early Return
|
|
is_optimal: true
|
|
code: |
|
|
def is_valid(s: str) -> bool:
|
|
# Quick check: odd length can never be valid
|
|
if len(s) % 2 != 0:
|
|
return False
|
|
|
|
stack = []
|
|
pairs = {'(': ')', '{': '}', '[': ']'}
|
|
|
|
for char in s:
|
|
if char in pairs:
|
|
stack.append(pairs[char])
|
|
elif not stack or stack.pop() != char:
|
|
return False
|
|
|
|
return not stack
|
|
explanation: |
|
|
Optimization: push the expected closing bracket instead of the opening one.
|
|
This simplifies the comparison when we encounter a closing bracket.
|