Valid Parenthesis String
Difficulty: Medium
You're given a string containing only three kinds of characters: '(', ')', and '*'. Each '*' is a wildcard that can be treated as either '(', or ')', or an empty string (nothing at all) - you choose, independently for each '*'.
Determine whether there's some way to resolve all the wildcards so that the resulting string is a valid parentheses string (every open paren has a matching close paren later, properly nested).
Examples
Input: s = "()"
Output: true
Already valid with no wildcards involved.
Input: s = "(*)"
Output: true
Treat the "*" as an empty string, leaving "()", which is valid.
Input: s = "(*))"
Output: true
Treat the "*" as "(", giving "(())", which is valid.
Constraints
1 <= s.length <= 100
s[i] is '(', ')', or '*'
Approach
Trying every combination of wildcard meanings is exponential. Instead of tracking one exact count of unmatched open parens, track a range of possible counts: the minimum and maximum number of unmatched '(' you could have after processing each character, given every valid choice made so far for the wildcards seen.
Scanning left to right: '(' increases both the low and high bound; ')' decreases both; '*' decreases the low bound (treat it as ')') and increases the high bound (treat it as '('). If the high bound ever drops below 0, some ')' had nothing to match no matter what - fail immediately. If the low bound drops below 0, clamp it to 0, since a count can't really be negative - it just means not every path down that low is still viable, but higher paths might be. At the end, it's valid if the low bound can reach exactly 0.
Solutions
Brute Force - Try Every Wildcard Resolution (Recursion)
At each '*', branch into all 3 possible interpretations and recursively check if any full resolution produces a valid string.
function checkValidString(s) {
function isValid(i, openCount) {
if (openCount < 0) return false;
if (i === s.length) return openCount === 0;
const c = s[i];
if (c === "(") return isValid(i + 1, openCount + 1);
if (c === ")") return isValid(i + 1, openCount - 1);
// '*' — try treating it as '(', ')', or empty
return (
isValid(i + 1, openCount + 1) ||
isValid(i + 1, openCount - 1) ||
isValid(i + 1, openCount)
);
}
return isValid(0, 0);
}Time: O(3^n) in the worst case, one branch per wildcard interpretation · Space: O(n) recursion depth
Optimal - Track Range of Possible Open Counts
Instead of one open-paren count, track the minimum and maximum possible open count as you scan, widening the range for '*' and narrowing it for '(' and ')'.
function checkValidString(s) {
let low = 0;
let high = 0;
for (const c of s) {
if (c === "(") {
low++;
high++;
} else if (c === ")") {
low--;
high--;
} else {
low--;
high++;
}
if (high < 0) return false;
if (low < 0) low = 0;
}
return low === 0;
}Time: O(n) · Space: O(1)