In the ever present LeetCode grind that our industry puts us through I decided to solve the “Valid Parentheses” problem in LeetCode today. For a LeetCode problem it’s surprisingly practical and provides insight into how our IDEs know when we are missing a bracket within our code. Like other LeetCode problems it’s relatively straightforward once you pick the right data structure.
Before we dive in if you are the type that prefers videos over reading do check out this walk through on my Youtube channel.
The Problem
We are given a string containing the following characters: ‘(‘, ‘)’, ‘{‘, ‘}’, ‘[‘, and ‘]’. We need to write a function that determines if the input string is valid. The problem defines ‘valid’ as:
- Open brackets must be closed by the same type of brackets.
- Open brackets have to be closed in the right order.
- Every close bracket has a corresponding bracket of the same type.
Let’s work through some examples:
Example 1:
Input s = “[()]”
Output: true
This makes sense since the string starts with “[“ and is closed by “]”. Also the inner “()” closes itself since the opening and closing brackets are next to each other.
Example 2:
Input s = “([)]”
Output: false
This makes sense since the string starts with “(“ and is closed by “]”. Although both “[“ and “(“ characters have a matching pair in the string the ordering is wrong and they don’t close each other out properly.
Solution
The core challenge with this problem is keeping track of the ordering of the open brackets as they come in and checking that they get closed later down the line. If we had to brute force this we could repeatedly scan the string, remove matching pairs and work our way through the string. Normally I would like to write a brute force solution, but in this scenario the optimal solution is actually simpler and easier to understand. There is actually an ideal data structure for this. A stack! The idea is that we push any open brackets we see onto the stack and when we see a closed bracket we pop the stack to see if the corresponding bracket is there. If it is there then we know the string is valid. If not then we know it’s invalid. If we go through the entire string and we still have stuff left in our stack then we know the string isn’t valid as well.
So what does the pseudocode for this look like?
- Initialize an empty stack to keep track of ordering of brackets
- For each character in the input string:
- If it’s an open brace then push it onto the stack
- If it’s a closed brace
- If the stack is empty or if we pop the stack and it’s not the corresponding bracket then we return false.
- If the stack is empty the string is valid, otherwise it’s invalid
Now let’s code it out.
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for(int i = 0; i < s.length(); i++) {
char current = s.charAt(i);
switch (current) {
case '(':
case '{':
case '[':
stack.push(current);
break;
case ')':
if (stack.empty() || stack.pop() != '(') return false;
break;
case '}':
if (stack.empty() || stack.pop() != '{') return false;
break;
case ']':
if (stack.empty() || stack.pop() != '[') return false;
break;
default:
// do this in case LeetCode ever throws us a bad input
throw new IllegalArgumentException();
}
}
return stack.isEmpty();
}
If you are a seasoned LeetCoder you may prefer to write this solution using a map. That works too and the code is a bit cleaner. However in my experience I have found that switch statements to be more explicit and readable for a small character set. However for those who do prefer maps here is what that solution looks like:
public boolean isValid(String s) {
Map<Character, Character> map = new HashMap<>();
map.put(')', '(');
map.put('}', '{');
map.put(']', '[');
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (!map.containsKey(c)) {
stack.push(c);
} else (map.containsKey(c)) {
if (stack.isEmpty() || map.get(c) != stack.pop()) {
return false;
}
}
}
return stack.isEmpty();
}
Since we have to traverse the entire string at least once to know if it’s valid or not this solution has a time complexity of O(n). Also in the worst case scenario of all the characters being open brackets and stored on our stack then the space complexity of this solution is also O(n). This time complexity and space complexity holds true for both the switch statement and the map approach.
Key Takeaways
Although this problem is relatively straightforward I think it still teaches the critical skill of choosing the right data structure for the problem. Solving this problem without some stack-like data structure would be incredibly painful. Also, unlike most LeetCode problems it does actually have a use in the real world. Anyone who solves this can appreciate how their IDE’s bracket checking works. So I hope that the next time you see your IDE telling you that a bracket is missing you remember this problem.