Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions problems/0678-valid-parenthesis-string/analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# 0678. Valid Parenthesis String

[LeetCode Link](https://leetcode.com/problems/valid-parenthesis-string/)

Difficulty: Medium
Topics: String, Dynamic Programming, Stack, Greedy, Bracket Sequences
Acceptance Rate: 41.1%

## Hints

### Hint 1

Without the `'*'`, this is the classic "balanced parentheses" check: scan left to right and track how many `'('` are still open. The string is valid if that count never drops below zero and ends at zero. How does the `'*'` change what that single counter can be?

### Hint 2

Each `'*'` has three choices, so trying every combination is exponential. Instead of tracking *one* open count, think about tracking the *set* of open counts that are reachable after each character. Is that set always a contiguous range?

### Hint 3

Keep two numbers: `lo`, the minimum possible number of open `'('`, and `hi`, the maximum possible. On `'('` both go up; on `')'` both go down; on `'*'` `lo` goes down (treat it as `')'`) and `hi` goes up (treat it as `'('`). If `hi` ever drops below 0, there are too many `')'` — fail. Clamp `lo` at 0, since a negative open count is never a valid state. At the end, the string is valid exactly when `lo == 0`.

## Approach

**Greedy range tracking.**

Think of the open-parenthesis balance as a value that must stay `>= 0` at every prefix and end at exactly `0`. Because `'*'` can be `'('`, `')'`, or empty, after any prefix the balance could be one of several values. The key observation is that these reachable values always form a contiguous range `[lo, hi]` (each `'*'` adds -1, 0, or +1, which fills gaps), so we only need to store its two endpoints.

Scan the string:

- `'('`: `lo++`, `hi++`
- `')'`: `lo--`, `hi--`
- `'*'`: `lo--` (use it as `')'`), `hi++` (use it as `'('`)

After each step:

1. If `hi < 0`, even treating every `'*'` as `'('` cannot cover the `')'` seen so far → return `false`.
2. If `lo < 0`, set `lo = 0`. A negative balance is an invalid state, so we discard it; this corresponds to choosing "empty" instead of `')'` for some earlier `'*'`.

At the end, return `lo == 0`: zero must be inside the reachable range (`hi >= 0` is already guaranteed).

**Example:** `s = "(*))"`

| char | lo | hi |
|------|----|----|
| `(` | 1 | 1 |
| `*` | 0 | 2 |
| `)` | 0 (clamped from -1) | 1 |
| `)` | 0 (clamped from -1) | 0 |

`lo == 0` → `true`. (Here the `'*'` acts as `'('`.)

**Alternatives worth knowing:**
- *Two stacks* (indices of `'('` and of `'*'`): match each `')'` with a `'('` first, else a `'*'`; then pair leftover `'('` with `'*'` that appear *after* them. O(n) time, O(n) space.
- *Interval DP* `dp[i][j]` = whether `s[i..j]` is valid: O(n³) time — fine for n ≤ 100 but much slower.

## Complexity Analysis

Time Complexity: O(n) — a single pass over the string.
Space Complexity: O(1) — only two integer counters.

## Edge Cases

- **Leading `')'`** (e.g. `")("`): `hi` goes negative immediately; must fail fast rather than wait for a later `'('` to "fix" it.
- **Only stars** (e.g. `"***"`): every star can be empty, so it's valid; `lo` stays clamped at 0.
- **Unmatched `'('` with a star before it** (e.g. `"*("`): a `'*'` can't close a `'('` that comes *after* it; `lo` ends at 1 → `false`. This is the order constraint that naive counting misses.
- **Single character**: `"("` and `")"` are invalid, `"*"` is valid.
- **Stars needed as closers** (e.g. `"((*"`): only one star for two opens → `lo` ends at 1 → `false`.
69 changes: 69 additions & 0 deletions problems/0678-valid-parenthesis-string/problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
number: "0678"
frontend_id: "678"
title: "Valid Parenthesis String"
slug: "valid-parenthesis-string"
difficulty: "Medium"
topics:
- "String"
- "Dynamic Programming"
- "Stack"
- "Greedy"
- "Bracket Sequences"
acceptance_rate: 4113.3
is_premium: false
created_at: "2026-10-04T06:09:07.468526+00:00"
fetched_at: "2026-10-04T06:09:07.468526+00:00"
link: "https://leetcode.com/problems/valid-parenthesis-string/"
date: "2026-10-04"
---

# 0678. Valid Parenthesis String

Given a string `s` containing only three types of characters: `'('`, `')'` and `'*'`, return `true` _if_ `s` _is**valid**_.

The following rules define a **valid** string:

* Any left parenthesis `'('` must have a corresponding right parenthesis `')'`.
* Any right parenthesis `')'` must have a corresponding left parenthesis `'('`.
* Left parenthesis `'('` must go before the corresponding right parenthesis `')'`.
* `'*'` could be treated as a single right parenthesis `')'` or a single left parenthesis `'('` or an empty string `""`.





**Example 1:**


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


**Example 2:**


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


**Example 3:**


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


**Example 4:**


**Input:** s = "("
**Output:** false




**Constraints:**

* `1 <= s.length <= 100`
* `s[i]` is `'('`, `')'` or `'*'`.
31 changes: 31 additions & 0 deletions problems/0678-valid-parenthesis-string/solution_daily_20261004.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

// Approach: greedy range tracking.
// Track the minimum (lo) and maximum (hi) number of unmatched '(' possible
// after each prefix. '*' widens the range in both directions. If hi < 0 the
// string has too many ')' and can never be valid. lo is clamped at 0 since a
// negative balance is never a valid state. The string is valid iff lo == 0 at
// the end. O(n) time, O(1) space.
func checkValidString(s string) bool {
lo, hi := 0, 0
for i := 0; i < len(s); i++ {
switch s[i] {
case '(':
lo++
hi++
case ')':
lo--
hi--
default: // '*'
lo--
hi++
}
if hi < 0 {
return false
}
if lo < 0 {
lo = 0
}
}
return lo == 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import "testing"

func TestSolution(t *testing.T) {
tests := []struct {
name string
s string
expected bool
}{
{"example 1: simple pair", "()", true},
{"example 2: star as empty", "(*)", true},
{"example 3: star as open", "(*))", true},
{"example 4: single open", "(", false},
{"edge case: single close", ")", false},
{"edge case: single star", "*", true},
{"edge case: only stars", "***", true},
{"edge case: close before open", ")(", false},
{"edge case: star before open cannot close it", "*(", false},
{"edge case: not enough stars to close", "((*", false},
{"edge case: stars close multiple opens", "((**", true},
{"edge case: star as close at start invalid", "*)))", false},
{"edge case: long mixed valid", "(*()*)(*)*", true},
{"edge case: leetcode tricky", "(((((*(()((((*((**(((()()*)()()()*((((**)())*)*)))))))(())(()))())((*()()(((()((()*(())*(()**)()(())", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := checkValidString(tt.s)
if result != tt.expected {
t.Errorf("checkValidString(%q) = %v, want %v", tt.s, result, tt.expected)
}
})
}
}