Data structures and algorithms are often hidden in places we cannot see.
# 20. Valid Parentheses
LeetCode Problem Link (opens new window)
Given a string containing just the characters '(', ')', '{', '}', '[', ']', determine if the input string is valid.
A valid string must meet the following criteria:
- Open brackets must be closed by the same type of brackets.
- Open brackets must be closed in the correct order.
- Note that an empty string is also considered valid.
Example 1:
- Input:
() - Output: true
Example 2:
- Input:
()[]{} - Output: true
Example 3:
- Input:
(] - Output: false
Example 4:
- Input:
([)] - Output: false
Example 5:
- Input:
{[]} - Output: true
# Approach
# Additional Notes
Bracket matching is a classic problem solved using a stack.
The problem is similar to how we write code and ensure that brackets are ordered correctly, with each opening bracket having a corresponding closing bracket.
If you remember compiler principles, the process of handling brackets such as parentheses during lexical analysis is also done using stack data structures.
Another example is the Linux system command cd, which we're all familiar with.
cd a/b/c/../../
This command results in entering directory a, but how does the system know to end up in a? This is where the stack's application comes into play (this could also be a relevant interview question).
Thus, stacks are widely used in computer science.
Some students often wonder what use these data structures have, thinking they don't help in developing software. Most students refer to visual software such as apps or websites, which are very high-level applications. Many underlying functionalities are implemented using fundamental data structures and algorithms.
So, the application of data structures and algorithms is often hidden where we can't see them!
Let's get to the problem.
# Starting the Solution
Given the special properties of stack structures, they are particularly suitable for symmetry matching problems.
First, we need to clarify the mismatch situations of brackets in the string.
Some students start coding immediately in interviews upon seeing this problem and then find themselves getting more and more confused.
In such situations, I recommend analyzing all mismatch scenarios before writing any code to avoid ending up with erroneous logic.
To analyze, there are three main mismatch situations:
First, there may be extra opening brackets in the string, resulting in an invalid sequence.

Secondly, even if brackets aren't extra, the types may not match up.

Lastly, there may be extra closing brackets, leading to mismatches.

Our code should handle these three mismatch conditions to avoid issues. It demonstrates the importance of analysis before jumping into implementation.
The animation below illustrates this:

Situation 1: If we have traversed the string and the stack isn't empty, it means some opening brackets lack matching closing brackets, so we return false.
Situation 2: During the string traversal, if the intended character cannot be matched from the stack, we return false.
Situation 3: While traversing, if the stack is already empty and lacks matching characters, it means closing brackets do not have corresponding opening brackets, so we return false.
When are open and close brackets fully matched? It's when the string traversal completes, and the stack is empty, indicating all brackets are matched.
After this analysis, writing the code becomes straightforward.
Additionally, a useful trick is to push corresponding closing brackets on seeing open brackets, and we only need to compare the current element with the stack top. This approach simplifies implementation compared to pushing open brackets first.
Here's the C++ implementation:
class Solution {
public:
bool isValid(string s) {
if (s.size() % 2 != 0) return false; // If the length of s is odd, it can't be valid
stack<char> st;
for (int i = 0; i < s.size(); i++) {
if (s[i] == '(') st.push(')');
else if (s[i] == '{') st.push('}');
else if (s[i] == '[') st.push(']');
// Case 3: During traversal, the stack is already empty, and there are no matching characters left, so return false
// Case 2: During traversal, the intended match isn't found in the stack, so return false
else if (st.empty() || st.top() != s[i]) return false;
else st.pop(); // Matching character found, pop the top of the stack
}
// Case 1: After traversal, if the stack isn't empty, it means there are unmatched open brackets, so return false; otherwise return true
return st.empty();
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- Time complexity: O(n)
- Space complexity: O(n)
These tricks require exposure and practice to master and apply flexibly.
# Implementations in Other Languages
# Java:
class Solution {
public boolean isValid(String s) {
Deque<Character> deque = new LinkedList<>();
char ch;
for (int i = 0; i < s.length(); i++) {
ch = s.charAt(i);
// On encountering a left bracket, push the corresponding right bracket onto the stack
if (ch == '(') {
deque.push(')');
}else if (ch == '{') {
deque.push('}');
}else if (ch == '[') {
deque.push(']');
} else if (deque.isEmpty() || deque.peek() != ch) {
return false;
}else {
deque.pop();
}
}
// If the stack is empty at the end of traversal, it implies complete bracket matching
return deque.isEmpty();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Alternative solution
// Directly check if the corresponding half is at the stack’s top
class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for(char c : s.toCharArray()){
// If the corresponding character is found, proceed to match
if(c == ')' && !stack.isEmpty() && stack.peek() == '(')
stack.pop();
else if(c == '}' && !stack.isEmpty() && stack.peek() == '{')
stack.pop();
else if(c == ']' && !stack.isEmpty() && stack.peek() == '[')
stack.pop();
else
stack.push(c); // If unmatched, just push it
}
return stack.isEmpty();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Python:
# Method 1: Use only stack for space efficiency
class Solution:
def isValid(self, s: str) -> bool:
stack = []
for item in s:
if item == '(':
stack.append(')')
elif item == '[':
stack.append(']')
elif item == '{':
stack.append('}')
elif not stack or stack[-1] != item:
return False
else:
stack.pop()
return True if not stack else False
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Method 2: Use dictionary
class Solution:
def isValid(self, s: str) -> bool:
stack = []
mapping = {
'(': ')',
'[': ']',
'{': '}'
}
for item in s:
if item in mapping.keys():
stack.append(mapping[item])
elif not stack or stack[-1] != item:
return False
else:
stack.pop()
return True if not stack else False
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Go:
// Approach: Use a stack for matching brackets
// Time complexity O(n)
// Space complexity O(n)
func isValid(s string) bool {
// Use slice to simulate stack
stack := make([]rune, 0)
// m records left brackets matching specific right brackets
m := make(map[rune]rune)
m[')'] = '('
m[']'] = '['
m['}'] = '{'
// Traverse through the string
for _, c := range s {
// Left brackets directly push into the stack
if c == '(' || c == '[' || c == '{' {
stack = append(stack, c)
} else {
// If the stack is empty or unmatched, return false
if len(stack) == 0 || stack[len(stack)-1] != m[c] {
return false
}
// Simulate stack pop
stack = stack[:len(stack)-1]
}
}
// Return true if the stack is empty (complete matching)
return len(stack) == 0
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Ruby:
def is_valid(strs)
symbol_map = {')' => '(', '}' => '{', ']' => '['}
stack = []
strs.size.times {|i|
c = strs[i]
if symbol_map.has_key?(c)
top_e = stack.shift
return false if symbol_map[c] != top_e
else
stack.unshift(c)
end
}
stack.empty?
end
2
3
4
5
6
7
8
9
10
11
12
13
14
# JavaScript:
var isValid = function (s) {
const stack = [];
for (let i = 0; i < s.length; i++) {
let c = s[i];
switch (c) {
case '(':
stack.push(')');
break;
case '[':
stack.push(']');
break;
case '{':
stack.push('}');
break;
default:
if (c !== stack.pop()) {
return false;
}
}
}
return stack.length === 0;
};
// Simplified version
var isValid = function(s) {
const stack = [],
map = {
"(":")",
"{":"}",
"[":"]"
};
for(const x of s) {
if(x in map) {
stack.push(x);
continue;
};
if(map[stack.pop()] !== x) return false;
}
return !stack.length;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# TypeScript:
Version 1: Basic
function isValid(s: string): boolean {
let helperStack: string[] = [];
for (let i = 0, length = s.length; i < length; i++) {
let x: string = s[i];
switch (x) {
case '(':
helperStack.push(')');
break;
case '[':
helperStack.push(']');
break;
case '{':
helperStack.push('}');
break;
default:
if (helperStack.pop() !== x) return false;
break;
}
}
return helperStack.length === 0;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Version 2: Optimized
function isValid(s: string): boolean {
type BracketMap = {
[index: string]: string;
}
let helperStack: string[] = [];
let bracketMap: BracketMap = {
'(': ')',
'[': ']',
'{': '}'
}
for (let i of s) {
if (bracketMap.hasOwnProperty(i)) {
helperStack.push(bracketMap[i]);
} else if (i !== helperStack.pop()) {
return false;
}
}
return helperStack.length === 0;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Swift:
func isValid(_ s: String) -> Bool {
var stack = [String.Element]()
for ch in s {
if ch == "(" {
stack.append(")")
} else if ch == "{" {
stack.append("}")
} else if ch == "[" {
stack.append("]")
} else {
let top = stack.last
if ch == top {
stack.removeLast()
} else {
return false
}
}
}
return stack.isEmpty
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# C:
// Helper function: Checks if the top element of the stack and the input bracket form a pair. If not, return false
int notMatch(char par, char* stack, int stackTop) {
switch(par) {
case ']':
return stack[stackTop - 1] != '[';
case ')':
return stack[stackTop - 1] != '(';
case '}':
return stack[stackTop - 1] != '{';
}
return 0;
}
bool isValid(char * s){
int strLen = strlen(s);
// Allocate space for the stack
char stack[5000];
int stackTop = 0;
// Traverse the string
int i;
for(i = 0; i < strLen; i++) {
// Get the current character
char tempChar = s[i];
// If it's an opening bracket, push it to the stack
if(tempChar == '(' || tempChar == '[' || tempChar == '{')
stack[stackTop++] = tempChar;
// If it's a closing bracket and no matching opening bracket in the stack, return false
else if(stackTop == 0 || notMatch(tempChar, stack, stackTop))
return 0;
// Valid pair found, pop the stack
else
stackTop--;
}
// Return true if stack is empty, false otherwise
return !stackTop;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# C#:
public class Solution {
public bool IsValid(string s) {
var len = s.Length;
if(len % 2 == 1) return false; // If string length is odd, return false
// Initialize stack
var stack = new Stack<char>();
// Traverse the string
for(int i = 0; i < len; i++){
// On encountering a left bracket, push corresponding right bracket to the stack
if(s[i] == '('){
stack.Push(')');
}else if(s[i] == '['){
stack.Push(']');
}else if(s[i] == '{'){
stack.Push('}');
}
// If there are no matching characters left, return false
else if(stack.Count == 0 || stack.Pop() != s[i])
return false;
}
// Return true if stack is empty, false otherwise
return stack.Count == 0;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# PHP:
// https://www.php.net/manual/zh/class.splstack.php
class Solution
{
function isValid($s){
$stack = new SplStack();
for ($i = 0; $i < strlen($s); $i++) {
if ($s[$i] == "(") {
$stack->push(')');
} else if ($s[$i] == "{") {
$stack->push('}');
} else if ($s[$i] == "[") {
$stack->push(']');
// 2. During traversal, if no matching character is found, return false
// 3. If the stack is already empty and unmatched, return false
} else if ($stack->isEmpty() || $stack->top() != $s[$i]) {
return false;
} else {// Matching character found, pop
$stack->pop();
}
}
// 1. If traversal ends with unmatched open brackets, return false
return $stack->isEmpty();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Scala:
object Solution {
import scala.collection.mutable
def isValid(s: String): Boolean = {
if(s.length % 2 != 0) return false // If string length is odd, return false
val stack = mutable.Stack[Char]()
// Traverse the string
for (i <- s.indices) {
val c = s(i)
if (c == '(' || c == '[' || c == '{') stack.push(c)
else if(stack.isEmpty) return false // If no opening bracket, return false immediately
// Return false for the following unmet conditions
else if(c==')' && stack.pop() != '(') return false
else if(c==']' && stack.pop() != '[') return false
else if(c=='}' && stack.pop() != '{') return false
}
// Return true if stack is empty (all matched)
stack.isEmpty
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Rust:
impl Solution {
pub fn is_valid(s: String) -> bool {
if s.len() % 2 == 1 {
return false;
}
let mut stack = vec![];
let mut chars: Vec<char> = s.chars().collect();
while let Some(s) = chars.pop() {
match s {
')' => stack.push('('),
']' => stack.push('['),
'}' => stack.push('{'),
_ => {
if stack.is_empty() || stack.pop().unwrap() != s {
return false;
}
}
}
}
stack.is_empty()
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22