LeetCode Β· #678
Valid Parenthesis String Solution
Given a string with (, ), and * (wildcard: empty, (, or )), determine if the string can be valid.
01
Section One Β· Problem
Problem Statement
Each * can be (, ), or empty string. Return true if the string can be made valid as parentheses.
Example
Input: s = "(*)" Output: true // * can be empty β "()" is valid Input: s = "(*))" Output: true // * can be "(" β "(())" is valid
Constraints
β’ 1 β€ s.length β€ 100 β’ s[i] is '(', ')' or '*'
02
Section Two Β· Approach 1
Brute Force β O(3βΏ)
For each *, try all three options. Check if any assignment yields a valid string.
Problem: Exponential. Instead of tracking exact open count, track the range of possible open counts. If the range includes 0 at the end, it's valid.
Java β Recursive
class Solution {
public boolean checkValidString(String s) {
return check(s, 0, 0);
}
private boolean check(String s, int i, int open) {
if (open < 0) return false;
if (i == s.length()) return open == 0;
if (s.charAt(i) == '(') return check(s, i+1, open+1);
if (s.charAt(i) == ')') return check(s, i+1, open-1);
return check(s,i+1,open+1) || check(s,i+1,open-1) || check(s,i+1,open);
}
}
Python β Recursive
class Solution:
def checkValidString(self, s: str) -> bool:
def check(i, o):
if o < 0: return False if i == len(s): return o == 0 if s[i] == '(': return check(i+1, o+1)
if s[i] == ')': return check(i+1, o-1)
return check(i+1,o+1) or check(i+1,o-1) or check(i+1,o)
return check(0, 0)
03
Section Three Β· Approach 2
Greedy Min/Max β O(n) time, O(1) space
Track lo (minimum possible open parens) and hi (maximum possible open parens). ( increments both. ) decrements both. * decrements lo, increments hi. Clamp lo at 0. If hi < 0, impossible.
π‘ Mental model: Imagine balancing a scale where
( adds weight (open count) and ) removes it. A * is a wildcard weight β it could add, remove, or do nothing. Instead of tracking every possibility, track the range [lo, hi] of possible scale readings. If the range ever excludes 0 entirely (hi < 0), it's impossible. If the range includes 0 at the end (lo β€ 0 β€ hi, which means lo == 0 since we clamp), it's valid.Algorithm
lo = 0,hi = 0.- For each char:
(β lo++, hi++.)β lo--, hi--.*β lo--, hi++. - If
hi < 0: too many), return false. - Clamp
lo = max(lo, 0)(can't have negative open parens). - Return
lo == 0.
π― When to recognize this pattern: "Parentheses validation with wildcards." The signal: characters that can be multiple things + validity check. Min/max range tracking collapses exponential branching into a linear sweep. Also useful for fuzzy matching scenarios.
04
Section Four Β· Trace
Visual Walkthrough
Trace for s = "(*))".
Valid Parenthesis String β Min/Max Tracking
05
Section Five Β· Implementation
Code β Java & Python
Java β Greedy min/max
class Solution {
public boolean checkValidString(String s) {
int lo = 0, hi = 0;
for (char c : s.toCharArray()) {
lo += c == '(' ? 1 : -1;
hi += c != ')' ? 1 : -1;
if (hi < 0) return false; // too many )
lo = Math.max(lo, 0);
}
return lo == 0;
}
}
Python β Greedy min/max
class Solution:
def checkValidString(self, s: str) -> bool:
lo = hi = 0 for c in s:
lo += 1 if c == '(' else -1
hi += 1 if c != ')' else -1 if hi < 0: return False
lo = max(lo, 0)
return lo == 0
06
Section Six Β· Analysis
Complexity Analysis
| Approach | Time | Space | Trade-off |
|---|---|---|---|
| Brute force | O(3βΏ) | O(n) | Try all * assignments |
| DP with memo | O(nΒ²) | O(nΒ²) | Memoize (index, openCount) |
| Greedy min/max β optimal | O(n) | O(1) | Two variables track range of possibilities |
07
Section Seven Β· Edge Cases
Edge Cases & Pitfalls
| Case | Input | Why It Matters |
|---|---|---|
| All stars | "***" | All can be empty. lo=0. Valid. |
| Star as close | "(*" | * = ) β "()". Valid. lo=0, hi=2 β clamp lo=0. |
| Too many closes | ")(" | hi goes negative at first char. Return false. |
| No stars | "(())" | Standard parentheses. lo tracks normally. |
| Unmatched opens | "(((" | lo=3 at end β 0. Return false. |
β Common Mistake: Only tracking
hi (maximum possible open count) and checking hi >= 0. This misses cases where lo > 0 at the end β meaning even in the best case, there are unmatched open parens. You need both lo and hi: lo for "can we match all opens?" and hi for "did we ever have too many closes?"