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
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 2267. Check if There Is a Valid Parentheses String Path

[LeetCode Link](https://leetcode.com/problems/check-if-there-is-a-valid-parentheses-string-path/)

Difficulty: Hard
Topics: Array, Dynamic Programming, Matrix, Bracket Sequences
Acceptance Rate: 48.9%

## Hints

### Hint 1

How do you check if a single parentheses string is valid without a stack? Think about a single integer counter. Then think about which grid-path technique you know for "moves only down or right".

### Hint 2

The validity of a path prefix is fully described by one number: the current balance (opens minus closes). Two different paths arriving at the same cell with the same balance are interchangeable for the future. That suggests adding the balance as an extra dimension to a grid DP.

### Hint 3

Define `reachable[i][j][b]` = "some path from `(0,0)` to `(i,j)` has balance exactly `b` and never went negative". Transition from the top and left neighbors by applying `+1` or `-1`. The answer is `reachable[m-1][n-1][0]`. Balance can never usefully exceed `(m+n-1)/2`, which keeps the state space small. Also: the path length `m+n-1` must be even — check that first.

## Approach

1. **Quick rejections.** Every path has exactly `m + n - 1` characters. An odd length can never be balanced. The first cell must be `'('` and the last must be `')'`.
2. **State.** For each cell, keep the set of balances reachable by some valid-so-far prefix ending at that cell. A balance is valid while it is `>= 0`.
3. **Transition.** For cell `(i, j)` with delta `+1` for `'('` and `-1` for `')'`, take every balance `b` reachable at `(i-1, j)` or `(i, j-1)` and mark `b + delta` if it is non-negative.
4. **Pruning.** If the balance is larger than the number of cells still left on the path, it can never come back to zero, so drop it. This also bounds balances by about `(m+n)/2`.
5. **Answer.** Return whether balance `0` is reachable at `(m-1, n-1)`.

Because transitions only look at the row above and the cell to the left, a single rolling row of sets is enough: when processing `(i, j)`, `dp[j]` still holds row `i-1` (the cell above) while `dp[j-1]` has already been updated to row `i` (the cell to the left).

**Why it works:** the future of a path depends only on where it is and its current balance, not on the exact characters so far. Grouping paths by `(cell, balance)` collapses exponentially many paths into polynomially many states.

**Example:** `grid = ["(()", "())"]` (length 4). Along the top row the balances are `{1}`, `{2}`, `{1}`. Row two: `(1,0)` gets `{0}`; `(1,1)` gets from top `{2}` and left `{0}` after `-1` → `{1}` (the `-1` from `0` is dropped); `(1,2)` gets from top `{1}` and left `{1}` after `-1` → `{0}`. Balance `0` is reachable, so the answer is `true`.

## Complexity Analysis

Time Complexity: O(m · n · (m + n))
Space Complexity: O(n · (m + n)) with a rolling row

## Edge Cases

- **Odd path length** (e.g. `1x1` or `3x3` grid): can never be valid; return `false` immediately.
- **First cell is `')'` or last cell is `'('`**: every path fails; quick rejection.
- **Single row or single column**: only one path exists; the DP must still handle missing top/left neighbors.
- **Balance dipping below zero mid-path**: a prefix like `"())"` must be discarded even if the total count later balances out.
- **Even length and correct endpoints but still no valid path**: e.g. `["())", ")))"]`, where every path goes negative, which shows that the quick checks alone aren't enough.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
number: "2267"
frontend_id: "2267"
title: " Check if There Is a Valid Parentheses String Path"
slug: "check-if-there-is-a-valid-parentheses-string-path"
difficulty: "Hard"
topics:
- "Array"
- "Dynamic Programming"
- "Matrix"
- "Bracket Sequences"
acceptance_rate: 4887.9
is_premium: false
created_at: "2026-09-29T05:58:08.749707+00:00"
fetched_at: "2026-09-29T05:58:08.749707+00:00"
link: "https://leetcode.com/problems/check-if-there-is-a-valid-parentheses-string-path/"
date: "2026-09-29"
---

# 2267. Check if There Is a Valid Parentheses String Path

A parentheses string is a **non-empty** string consisting only of `'('` and `')'`. It is **valid** if **any** of the following conditions is **true** :

* It is `()`.
* It can be written as `AB` (`A` concatenated with `B`), where `A` and `B` are valid parentheses strings.
* It can be written as `(A)`, where `A` is a valid parentheses string.



You are given an `m x n` matrix of parentheses `grid`. A **valid parentheses string path** in the grid is a path satisfying **all** of the following conditions:

* The path starts from the upper left cell `(0, 0)`.
* The path ends at the bottom-right cell `(m - 1, n - 1)`.
* The path only ever moves **down** or **right**.
* The resulting parentheses string formed by the path is **valid**.



Return `true` _if there exists a**valid parentheses string path** in the grid._ Otherwise, return `false`.



**Example 1:**

![](https://assets.leetcode.com/uploads/2022/03/15/example1drawio.png)


**Input:** grid = [["(","(","("],[")","(",")"],["(","(",")"],["(","(",")"]]
**Output:** true
**Explanation:** The above diagram shows two possible paths that form valid parentheses strings.
The first path shown results in the valid parentheses string "()(())".
The second path shown results in the valid parentheses string "((()))".
Note that there may be other valid parentheses string paths.


**Example 2:**

![](https://assets.leetcode.com/uploads/2022/03/15/example2drawio.png)


**Input:** grid = [[")",")"],["(","("]]
**Output:** false
**Explanation:** The two possible paths form the parentheses strings "))(" and ")((". Since neither of them are valid parentheses strings, we return false.




**Constraints:**

* `m == grid.length`
* `n == grid[i].length`
* `1 <= m, n <= 100`
* `grid[i][j]` is either `'('` or `')'`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

// Approach: DP over (row, col, balance).
// A path string is valid iff its running balance ('(' = +1, ')' = -1) never
// drops below zero and ends at exactly zero. Every path has length m+n-1, so
// it must be even. For each cell we track the set of balances reachable by
// some path from (0,0), using a rolling row of boolean sets. A balance above
// the number of remaining cells can never return to zero, so it is pruned.
// Time: O(m*n*(m+n)), Space: O(n*(m+n)).
func hasValidPath(grid [][]byte) bool {
m, n := len(grid), len(grid[0])
total := m + n - 1
if total%2 == 1 || grid[0][0] == ')' || grid[m-1][n-1] == '(' {
return false
}

maxBal := total/2 + 1
// dp[j][b] is true if balance b is reachable at (current row, j).
dp := make([][]bool, n)
for j := range dp {
dp[j] = make([]bool, maxBal+1)
}

for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
delta := 1
if grid[i][j] == ')' {
delta = -1
}
remaining := total - (i + j + 1) // cells left after this one

next := make([]bool, maxBal+1)
if i == 0 && j == 0 {
next[1] = true
} else {
for b := 0; b <= maxBal; b++ {
// dp[j] still holds the cell above; dp[j-1] is already the cell to the left.
fromTop := i > 0 && dp[j][b]
fromLeft := j > 0 && dp[j-1][b]
if !fromTop && !fromLeft {
continue
}
nb := b + delta
if nb < 0 || nb > remaining || nb > maxBal {
continue
}
next[nb] = true
}
}
dp[j] = next
}
}

return dp[n-1][0]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import "testing"

func toGrid(rows []string) [][]byte {
grid := make([][]byte, len(rows))
for i, r := range rows {
grid[i] = []byte(r)
}
return grid
}

func TestHasValidPath(t *testing.T) {
tests := []struct {
name string
grid []string
expected bool
}{
{"example 1: multiple valid paths exist", []string{"(((", ")()", "(()", "(()"}, true},
{"example 2: both paths start with ')'", []string{"))", "(("}, false},
{"edge case: single cell has odd length", []string{"("}, false},
{"edge case: 1x2 simple pair", []string{"()"}, true},
{"edge case: 1x2 reversed pair", []string{")("}, false},
{"edge case: odd path length 2x2", []string{"()", ")("}, false},
{"edge case: single column valid", []string{"(", "(", ")", ")"}, true},
{"edge case: last cell is '('", []string{"((", ")("}, false},
{"edge case: 3x3 grid has odd path length", []string{"(()", ")()", "())"}, false},
{"edge case: 2x3 valid path", []string{"(()", "())"}, true},
{"edge case: 2x3 even length and good endpoints but no valid path", []string{"())", ")))"}, false},
{"edge case: 3x4 valid path requires correct turns", []string{"(()(", ")(()", "()))"}, true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := hasValidPath(toGrid(tt.grid))
if result != tt.expected {
t.Errorf("got %v, want %v", result, tt.expected)
}
})
}
}