Skip to content
JZLeetCode
Go back

LeetCode 678 Valid Parenthesis String

Table of contents

Open Table of contents

Description

Question Links: LeetCode 678

Given a string s containing only three types of characters: '(', ')' and '*', return true if s is valid.

The following rules define a valid string:

Example 1:

Input: s = "()"
Output: true

Example 2:

Input: s = "(*)"
Output: true

Example 3:

Input: s = "(*))"
Output: true

Constraints:

Idea 1: Greedy Min/Max

Instead of branching on every *, we track the range of possible open-paren counts: [lo, hi].

If hi < 0, too many ) even in the most optimistic scenario → invalid. We clamp lo ≥ 0 because we never “need” negative opens. At the end, valid iff lo == 0 (some assignment of * makes it perfectly balanced).

s = "( * ) )"
     lo: 1  0  0  0     clamped to 0 when negative
     hi: 1  2  1  0     never goes negative

lo == 0 at end → valid ✓

Complexity: Time O(n)O(n) — single pass. Space O(1)O(1).

Idea 2: Two-Pass Greedy

Scan left-to-right treating every * optimistically as (—if balance ever drops below 0, there’s no way to fix excess ). Then scan right-to-left treating every * as )—if balance drops below 0, there are unmatched (.

Both passes must survive for the string to be valid.

Complexity: Time O(n)O(n) — two passes. Space O(1)O(1).

Java

// Solution 1: Greedy min/max. O(n) time, O(1) space.
public static boolean checkValidString(String s) {
    int lo = 0; // min possible open count
    int hi = 0; // max possible open count
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (c == '(') {
            lo++;
            hi++;
        } else if (c == ')') {
            lo--;
            hi--;
        } else {
            lo--; // treat '*' as ')'
            hi++; // treat '*' as '('
        }
        if (hi < 0) {
            return false;
        }
        lo = Math.max(lo, 0);
    }
    return lo == 0;
}

// Solution 2: Two-pass greedy. O(n) time, O(1) space.
public static boolean checkValidStringTwoPass(String s) {
    int balance = 0;
    for (int i = 0; i < s.length(); i++) { // O(n)
        char c = s.charAt(i);
        if (c == '(' || c == '*') {
            balance++;
        } else {
            balance--;
        }
        if (balance < 0) {
            return false;
        }
    }
    balance = 0;
    for (int i = s.length() - 1; i >= 0; i--) { // O(n)
        char c = s.charAt(i);
        if (c == ')' || c == '*') {
            balance++;
        } else {
            balance--;
        }
        if (balance < 0) {
            return false;
        }
    }
    return true;
}

Python

# Solution 1: Greedy min/max. O(n) time, O(1) space.
class Solution:
    def checkValidString(self, s: str) -> bool:
        lo = 0  # min possible open '(' count
        hi = 0  # max possible open '(' count
        for c in s:  # O(n)
            if c == '(':
                lo += 1
                hi += 1
            elif c == ')':
                lo -= 1
                hi -= 1
            else:  # '*' can be '(', ')' or empty
                lo -= 1  # treat as ')'
                hi += 1  # treat as '('
            if hi < 0:  # too many ')' even treating all '*' as '('
                return False
            lo = max(lo, 0)  # lo can't go negative
        return lo == 0

# Solution 2: Two-pass greedy. O(n) time, O(1) space.
class Solution2:
    def checkValidString(self, s: str) -> bool:
        balance = 0
        for c in s:  # O(n) left to right
            if c == '(' or c == '*':
                balance += 1
            else:
                balance -= 1
            if balance < 0:
                return False
        balance = 0
        for c in reversed(s):  # O(n) right to left
            if c == ')' or c == '*':
                balance += 1
            else:
                balance -= 1
            if balance < 0:
                return False
        return True

C++

// Solution 1: Greedy min/max. O(n) time, O(1) space.
static bool checkValidString(const string &s) {
    int lo = 0, hi = 0;
    for (char c : s) { // O(n)
        if (c == '(') { lo++; hi++; }
        else if (c == ')') { lo--; hi--; }
        else { lo--; hi++; }
        if (hi < 0) return false;
        lo = max(lo, 0);
    }
    return lo == 0;
}

// Solution 2: Two-pass greedy. O(n) time, O(1) space.
static bool checkValidStringTwoPass(const string &s) {
    int balance = 0;
    for (int i = 0; i < (int)s.size(); i++) { // O(n)
        if (s[i] == '(' || s[i] == '*') balance++;
        else balance--;
        if (balance < 0) return false;
    }
    balance = 0;
    for (int i = (int)s.size() - 1; i >= 0; i--) { // O(n)
        if (s[i] == ')' || s[i] == '*') balance++;
        else balance--;
        if (balance < 0) return false;
    }
    return true;
}

Rust

// Solution 1: Greedy min/max. O(n) time, O(1) space.
pub fn check_valid_string(s: String) -> bool {
    let mut lo = 0i32;
    let mut hi = 0i32;
    for c in s.chars() { // O(n)
        match c {
            '(' => { lo += 1; hi += 1; }
            ')' => { lo -= 1; hi -= 1; }
            _ => { lo -= 1; hi += 1; } // '*' wildcard
        }
        if hi < 0 { return false; }
        lo = lo.max(0);
    }
    lo == 0
}

// Solution 2: Two-pass greedy. O(n) time, O(1) space.
pub fn check_valid_string_two_pass(s: String) -> bool {
    let bytes = s.as_bytes();
    let mut balance = 0i32;
    for &b in bytes.iter() { // O(n)
        if b == b'(' || b == b'*' { balance += 1; }
        else { balance -= 1; }
        if balance < 0 { return false; }
    }
    balance = 0;
    for &b in bytes.iter().rev() { // O(n)
        if b == b')' || b == b'*' { balance += 1; }
        else { balance -= 1; }
        if balance < 0 { return false; }
    }
    true
}
Share this post on:

Next Post
LeetCode 337 House Robber III