Read-only web browser

Solutions

Browse the interview solutions folder with syntax highlighting.

valid-parenthesis-string.py

greedy/valid-parenthesis-string.py · Python · 3.3 KiB · 2026-04-19 08:43

Back to folder
# 2 stacks (1 for '(' and 1 for '*'), greedy (counting min and max possible open parentheses), DFS recursive, top-down DP, bottom-up DP
# greedy

# Stack - O(n) time, O(n) space
def checkValidString(s: str) -> bool:
    stackpars = []
    stackstar = []

    for i, c in enumerate(s):

        if c == '(':
            stackpars.append(i)
        elif c == '*':
            stackstar.append(i)
        elif c == ')':

            if not stackpars and not stackstar:
                return False
            
            if stackpars:
                stackpars.pop()
            elif stackstar:
                stackstar.pop()
    
    while stackstar and stackpars:
        # NB: any remaining ( should be matched by a * appearing AFTER it
        if stackpars.pop() > stackstar.pop():
            return False

    return not stackpars


# Greedy - O(n) time, O(1) space
def checkValidString(s: str) -> bool:
    # min and max possible of open unmatched parenthesis at any point
    openLeftMin = 0
    openLeftMax = 0

    for c in s:
        if c == '(':
            openLeftMax += 1
            openLeftMin += 1
        elif c == ')':
            openLeftMax -= 1
            openLeftMin -= 1
        else: # '*'
            openLeftMin -= 1 # )
            openLeftMax += 1 # (

        if openLeftMax < 0: # too many closing parens
            return False
        if openLeftMin < 0:
            openLeftMin = 0
    
    return openLeftMin == 0

# DFS recursive - O(3^n) time, O(n) space
def checkValidString(s: str) -> bool:

    def dfs(i: int, opened: int) -> bool:
        if opened < 0:
            return False 
        if i == len(s):
            return opened == 0

        if s[i] == '(':
            return dfs(i+1, opened+1)
        elif s[i] == ')':
            return dfs(i+1, opened-1)
        else: # s[i] == '*'
            res = False
            res |= dfs(i+1, opened)
            res |= dfs(i+1, opened-1)
            res |= dfs(i+1, opened+1)
            return res

    return dfs(0, 0)


# Top-down DP - O(n^2) time, O(n^2) space
# n^2 states (opened and i both range 0->n), each computed once
def checkValidString(s: str) -> bool:
    dp = {}

    def dfs(i: int, opened: int) -> bool:
        if opened < 0:
            return False 
        elif i == len(s):
            return opened == 0
        elif (i,opened) in dp:
            return dp[(i,opened)]

        if s[i] == '(':
            res = dfs(i+1, opened+1)
        elif s[i] == ')':
            res = dfs(i+1, opened-1)
        else: # s[i] == '*'
            res = False
            res |= dfs(i+1, opened)
            res |= dfs(i+1, opened-1)
            res |= dfs(i+1, opened+1)
        dp[(i,opened)] = res
        return res

    return dfs(0, 0)


# Bottom-up DP - O(n^2) time, O(n^2) space
def checkValidString(s: str) -> bool:
    n = len(s)
    # dp[i][opened]
    dp = [[False] * (n+1) for _ in range(n+1)]
    # Base case: last char and 0 opened
    dp[n][0] = True

    for i in range(n-1, -1, -1):
        for o in range(0, n):
            if s[i] == '(':
                dp[i][o] = dp[i+1][o+1]
            elif s[i] == ')':
                dp[i][o] = dp[i+1][o-1]
            elif s[i] == '*':
                res = False
                res |= dp[i+1][o+1]
                res |= dp[i+1][o-1]
                res |= dp[i+1][o]
                dp[i][o] = res

    return dp[0][0]