150NeetCode 150

129.Valid Parenthesis String

MediumGreedy ยท 8/8

Problem

Given a string s containing only '(', ')' and '*', return true if it can be a valid parentheses string.

'*' is a wildcard: it may be treated as '(', as ')', or as an empty string. A string is valid when every '(' has a matching ')' that comes after it, and every ')' has a matching '(' before it.

Examples

Input:  s = "()"
Output: true
Input:  s = "(*)"
Output: true
Explanation: Treat '*' as empty.
Input:  s = "(*))"
Output: true
Explanation: Treat '*' as '(' -> "(())".
Input:  s = "*("
Output: false
Explanation: The '(' has nothing after it to close it.

Constraints

  • 1 <= s.length <= 100
  • s[i] is '(', ')' or '*'.

๐Ÿ’ก Key Insight

Without wildcards, we would track a single open count. With wildcards, the count is no longer one number but a range of possibilities [lo, hi]: lo assumes every * closes (or vanishes when nothing is open), hi assumes every * opens. If hi ever drops below 0, there are too many ')' no matter what; clamp lo at 0 because we may choose a * to be empty instead. The string is valid iff lo == 0 at the end โ€” some choice of wildcards closes everything.

Brute Force Recursion

TimeO(3^n)SpaceO(n)

Intuition

Try all three interpretations of each * while tracking the number of unmatched '('. A branch dies if the count goes negative. At the end of the string the branch succeeds only with open == 0.

Algorithm

  1. check(i, open): if open < 0 return false; if i == n return open == 0.
  2. '(' โ†’ check(i + 1, open + 1); ')' โ†’ check(i + 1, open - 1).
  3. '*' โ†’ any of check(i + 1, open + 1), check(i + 1, open - 1), check(i + 1, open).

Visualization

s = "(*)"
check(0,0) '(' -> check(1,1)
check(1,1) '*' -> as '(' check(2,2) -> ')' -> check(3,1) false
               -> as ')' check(2,0) -> ')' -> check(3,-1) false
               -> empty  check(2,1) -> ')' -> check(3,0) true

Code

java
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; // a ')' with nothing to match
        }
        if (i == s.length()) {
            return open == 0;
        }
        char c = s.charAt(i);
        if (c == '(') {
            return check(s, i + 1, open + 1);
        }
        if (c == ')') {
            return check(s, i + 1, open - 1);
        }
        // '*' : try '(', ')' and empty
        return check(s, i + 1, open + 1)
            || check(s, i + 1, open - 1)
            || check(s, i + 1, open);
    }
}

Complexity

  • Time: O(3^n) โ€” three branches per wildcard.
  • Space: O(n) โ€” recursion depth.

Complexity Comparison

Approach Time Space
Brute Force Recursion O(3^n) O(n)
Memoized DP O(nยฒ) O(nยฒ)
Two Stacks O(n) O(n)
Greedy Range of Open Counts O(n) O(1)

Common Pitfalls

  • Counting stars as a pool without considering position โ€” "*(" has one star and one '(' but is invalid.
  • Returning hi == 0 or lo == 0 && hi == 0 at the end; the correct test is lo == 0.
  • Forgetting to clamp lo at 0, which lets a negative minimum hide a valid configuration.
  • In the two-stack approach, preferring a '*' over a real '(' when closing โ€” that wastes flexible characters.