top of page

Valid Parentheses

  • mosesg1123
  • May 5
  • 3 min read

Checking whether a string of parentheses is valid is one of the most common warm‑ups in interviews. It’s directly applicable to parsing code, validating expressions, and ensuring balanced delimiters in markup languages. Today, I’ll walk through how I, as a candidate, would think out loud to arrive at a robust solution.


Problem Statement

Given a string s containing only the characters (, ), {, }, [ and ], determine if the input string is valid. An input string is valid if:

  • Open brackets are closed by the same type of brackets.

  • Open brackets are closed in the correct order.

  • Every closing bracket has a corresponding opening bracket.

Return true if s is valid, false otherwise.


1. Clarify Requirements Before Jumping Into Code

  • Are there any other characters in the string? Only the six bracket characters.

  • Is the empty string valid? Yes, it should return true.

  • Should single-character strings return false? Yes, since no match possible.

  • Do nested/mixed types count? Yes—({[]}) is valid.

  • Expected time/space? Aim for O(n) time, extra O(n) space is acceptable.


2. Identify the Category of the Question

This is a string parsing problem using a stack data structure. Common patterns in this category include:

  • Stack-based matching for nested structures

  • Two-pointer for palindrome or reverse checks

  • Hash‑map lookup for matching pairs

  • Recursion for nested expression evaluation

Validating matching delimiters almost always suggests a stack for LIFO ordering of open brackets.


3. Consider a Brute‑Force Approach to Understand the Problem

To brute-force this question, I could repeatedly replace occurrences of (), [], and {} with the empty string until no more replacements occur. If the final string is empty, it’s valid.

  • Time Complexity: Each replacement scan is O(n), and in the worst case we do ~n/2 replacements → O(n²).

  • Space Complexity: O(n) for new strings.

  • Drawbacks: Quadratic time; inefficient for long strings.


4. Brainstorm More Solutions

To improve on O(n²):

  1. Counter per type: Keep counters for each bracket type and ensure they never go negative.

    • Pros: O(n) time, O(1) space.

    • Cons: Doesn’t handle interleaving: ([)] appears balanced in counts but is invalid.

  2. Recursive parse: Write a recursive function that consumes matching pairs and advances.

    • Pros: Elegant for nested rules.

    • Cons: Complex to implement correctly for mixed types, risk of deep recursion.

  3. Stack with pairs:

    • I know that ultimately, this problem comes down to comparing pairs of characters. Every time I see a closing paren, for example, it must have been immediately proceeded by a closing paren. If it were any other character, then the string would be invalid.

    • So, I'll traverse the string, and push every opening bracket onto a stack. When I encounter a closing bracket, pop from the stack and check if I have a valid pair. If it doesn’t match or the stack is empty, then return false.

    • At the end, check the stack is empty.

    • Pros: O(n) time, O(n) space, handles all ordering and nesting edge cases.


5. Discuss Trade‑Offs Between Your Solutions

Approach

Time

Space

Pros

Cons

Replace pairs (brute)

O(n²)

O(n)

Very simple

Too slow for large inputs

Counters per type

O(n)

O(1)

Constant space

Fails interleaving cases

Recursive parsing

O(n)

O(n)

Clean for pure nesting

Complex, recursion depth issue

Stack + map (optimal)

O(n)

O(n)

Handles all cases, clear logic

Uses extra stack/map space

Stack + map hits the sweet spot for correctness and performance.


6. Write Pseudocode to Structure Your Thoughts

function isValid(s):
  create empty stack
  map = { ')':'(', ']':'[', '}':'{' }

  for each char c in s:
    if c is opening bracket:
      push c onto stack
    else if c is closing bracket:
      if stack is empty or stack.pop() != map[c]:
        return false

  return stack is empty

7. Consider Edge Cases

  • Empty string → returns true.

  • Single character → returns false.

  • Starts with closing bracket, e.g. ")(" → false.

  • Correct counts but wrong order, e.g. "([)]" → false.

  • Long valid nested chain, e.g. "({[]})" → true.


8. Write Full Code Syntax

import java.util.Deque;
import java.util.ArrayDeque;
import java.util.Map;
import java.util.HashMap;

public class Solution {
    public boolean isValid(String s) {
        Deque<Character> stack = new ArrayDeque<>();
        Map<Character, Character> pairs = new HashMap<>();
        pairs.put(')', '(');
        pairs.put(']', '[');
        pairs.put('}', '{');

        for (char c : s.toCharArray()) {
            if (pairs.containsValue(c)) {
                stack.push(c);
            } else if (pairs.containsKey(c)) {
                if (stack.isEmpty() || stack.pop() != pairs.get(c)) {
                    return false;
                }
            } else {
                // unexpected character
                return false;
            }
        }
        return stack.isEmpty();
    }
}

9. Test Your Code

public static void main(String[] args) {
    Solution sol = new Solution();

    System.out.println(sol.isValid("") == true);
    System.out.println(sol.isValid("()") == true);
    System.out.println(sol.isValid("()[]{}") == true);
    System.out.println(sol.isValid("(]") == false);
    System.out.println(sol.isValid("([)]") == false);
    System.out.println(sol.isValid("{[]}") == true);
    System.out.println(sol.isValid("]") == false);
    System.out.println(sol.isValid("(((((") == false);

    System.out.println("All tests executed");
}

10. Key Lessons to Remember for Future Questions

  • Stack patterns are ideal for LIFO matching problems like parentheses and HTML tags.

  • A mapping of closing→opening characters keeps matching checks O(1).

  • Brute‑force string replacements clarify correctness but highlight performance pitfalls.

  • Clear pseudocode that mirrors code structure prevents logic errors under pressure.

  • Discussing multiple approaches and trade‑offs shows depth before implementing the optimal solution.


Good Luck and Happy Coding!

Recent Posts

See All
Merge Intervals

"Merge Intervals" is one of those classic algorithm problems that shows up frequently in technical interviews. It's a great test of your...

 
 
 
Jump Game II

Jump Game II is a classic follow-up to the original Jump Game problem. It’s not just about whether you can reach the end... now you have to do it in the fewest jumps possible! That small change turns

 
 
 
Jump Game

The "Jump Game" question is a popular one for interviews because it tests your ability to think greedily and work with dynamic movement through an array. It's a great warm-up for range-based greedy lo

 
 
 

Comments


Drop Me a Line, Let Me Know What You Think

Thanks for submitting!

© 2023 by Train of Thoughts. Proudly created with Wix.com

bottom of page