Start learning free

LESSON 291 OF 338 · STACK

Valid Parentheses in Go: O(n) with a Stack of Bytes

Practice O(n) Read the full explanation

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.

Example 1:

Input: s = "()"
Output: true

Example 2:

Input: s = "()[]{}"
Output: true

Example 3:

Input: s = "(]"
Output: false

Example 4:

Input: s = "([)]"
Output: false

Example 5:

Input: s = "{[]}"
Output: true

Constraints:

  • 1 <= s.length <= 104
  • s consists of parentheses only '()[]{}'.

Note:

Your algorithm should run in O(n) time and use O(n) extra space.


STEP 1 OF 12
    Ctrl + Enter to check

    GO · STACK

    Valid Parentheses in Go, explained

    Problem overview

    The function isValid receives a string made only of the characters (, ), {, }, [ and ]. It returns true when the string is valid and false otherwise. A string is valid when every opening bracket is closed by a bracket of the same type, and the brackets are closed in the right order: the most recently opened one must be closed first.

    Take the examples. "()[]{}" is valid: each bracket is closed right after it opens. "{[]}" is valid because the inner pair closes before the outer one. "(]" is invalid because the types differ, and "([)]" is invalid because the ) arrives while the [ opened later is still open. The input has between 1 and 10,000 characters, and the lesson asks for O(n) time and O(n) extra space.

    Approach: a stack of open brackets

    The rule "the last opened bracket must close first" is exactly how a stack behaves: last in, first out. Walk through the string from left to right:

    • An opening bracket is pushed onto the stack. It waits for its partner.
    • A closing bracket must match the bracket on top of the stack. Pop the top and compare. If the stack is empty, or the types differ, the string is invalid.
    • When the walk ends, the stack must be empty. Anything left was opened and never closed.

    A simpler idea, counting the opening and closing brackets of each type, accepts "([)]", because the counts balance even though the order is wrong. The stack keeps the order, which is why it is needed.

    Each character is pushed at most once and popped at most once, so the work is linear.

    Step by step

    1. Build a lookup table named closingBrackets, a map[byte]byte from each opening bracket to the closing bracket that matches it. It does two jobs: its keys tell opening brackets from closing ones, and its values say what must close each opener.
    2. Create the stack as an empty slice of bytes. The end of the slice is the top.
    3. Loop over the indices of input with a classic for loop and take the current character as bracket := input[i].
    4. Check whether it is an opener with the two-value map lookup. Only the ok flag matters, so the value is ignored with _.
    5. Push openers with append, assigning the result back to stack.
    6. Handle closers in the else branch. First, if the stack is empty there is nothing to match, so return false.
    7. Read the top at index len(stack)-1 and pop it by reslicing to stack[:len(stack)-1].
    8. Compare. The only valid closer for that opener is closingBrackets[openBracket]. If bracket is anything else, return false.
    9. After the loop, return whether the stack is empty. Leftover openers make the string invalid.

    The solution in Go

    func isValid(input string) bool {
        closingBrackets := map[byte]byte{
            '(': ')',
            '{': '}',
            '[': ']',
        }
        stack := []byte{}
    
        for i := 0; i < len(input); i++ {
            bracket := input[i]
            if _, ok := closingBrackets[bracket]; ok {
                stack = append(stack, bracket)
            } else {
                if len(stack) == 0 {
                    return false
                }
                openBracket := stack[len(stack)-1]
                stack = stack[:len(stack)-1]
                if bracket != closingBrackets[openBracket] {
                    return false
                }
            }
        }
    
        return len(stack) == 0
    }
    

    The Go details worth a closer look:

    • Indexing a string gives a byte. input[i] has type byte (an alias of uint8), not a character. Printing it shows a number: "("[0] prints as 40. That is why the map is map[byte]byte, and why the literal keys such as '(' work: an untyped rune constant that fits in a byte converts to byte automatically. Bytes are enough here because every bracket is a single byte.
    • The comma-ok lookup. Reading a missing key from a map does not fail; it returns the zero value, here 0. _, ok := closingBrackets[bracket] asks the question directly, and ok is false for a key that is absent. Declaring ok in the if header limits it to that statement.
    • The slice as a stack. append may allocate a bigger backing array, so its result always goes back into stack. The pop, stack[:len(stack)-1], copies nothing: it shortens the length and leaves the old element behind in the array, which is harmless here.
    • else placement. Go requires else on the same line as the closing brace of the if.
    • Returning a comparison. len(stack) == 0 is already a bool, so no if is needed.

    Complexity

    Time: O(n). The loop visits each character once, and a map lookup, a push and a pop each take constant time on average.

    Space: O(n). In the worst case, such as "((((((", every character is an opener and the stack grows to n elements. The map holds three fixed entries.

    Edge cases and common mistakes

    • A closer with an empty stack. For ")" or "())" the stack is empty when the closer arrives. Reading stack[len(stack)-1] would then index position -1 and panic, so the emptiness check must come first.
    • Leftover openers. "(" and "(()" pass every comparison in the loop. Only the final len(stack) == 0 rejects them.
    • Odd length. A valid string always has an even length, since brackets come in pairs. The stack logic already handles this, but it explains why "(((" can never be valid.
    • Counting instead of stacking. Checking only that each type appears equally often wrongly accepts "([)]".
    • Comparing the wrong pair. The popped opener is the key in the map, so the comparison is bracket != closingBrackets[openBracket]. Comparing bracket with openBracket itself would fail every valid string.
    • Using a string for the map. With input[i] as the lookup key, a map[string]string does not compile, because a byte is not a string.

    Testing

    • "()" gives true.
    • "()[]{}" gives true.
    • "{[]}" gives true.
    • "(]" gives false.
    • "([)]" gives false.
    • "((" gives false.
    • ")" gives false.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free