Find the length of the longest valid (well-formed) parentheses substring. A stack of indices — seeded with -1 as a base — turns each ) into an O(1) span measurement: i − stack.top().
Given a string of only ( and ), return the length of the longest valid (correctly matched) parentheses substring. For ")()())" the answer is 4 — the substring "()()" at indices 1..4. For "(()" it is 2 (the inner "()"), and for "" it is 0.
) successfully closes an opener, every position after that base up to the current i is valid, so the run length is simply i − base, where base = stack.top(). No re-scanning: one subtraction gives the length of the valid stretch ending here.-1 onto an empty stack. It is the index just before a valid run could start, so the first valid span measures correctly as i − (−1).(. Push its index i. We may need to close it later.). Pop first. If the stack is now empty, this ) had no opener — push i as the new base. Otherwise the pop matched an opener: update best = max(best, i − stack.top()).best is the longest valid length.-1 sentinel is what lets a valid run that starts at index 0 measure as i − (−1) instead of mis-counting. When a stray ) empties the stack, it becomes the new base, so future spans are measured from the right place.You can drop the stack entirely with two linear sweeps of counters. Left-to-right, track open and close counts: when equal, candidate length is 2·close; if close > open, reset both to 0. Then sweep right-to-left with the roles reversed (resetting when open > close) to catch runs like "(()" that never re-balance left-to-right. That is O(n) time and O(1) space. Dynamic programming (a dp[i] array of the longest valid run ending at i) also works in O(n) time, O(n) space.
")()())". Seed the stack with -1 as a base index and set best = 0.1function longestValidParentheses(s: string): number {2▶ const stack: number[] = [-1]; // base index: last unmatched position3▶ let best = 0;45 for (let i = 0; i < s.length; i++) {6 if (s[i] === '(') {7 stack.push(i); // remember this opener's index8 } else {9 stack.pop(); // try to close the most recent opener10 if (stack.length === 0) {11 stack.push(i); // no base left → i is the new base12 } else {13 best = Math.max(best, i - stack[stack.length - 1]);14 }15 }16 }17 return best;18}
function longestValidParentheses(s: string): number {
const stack: number[] = [-1]; // base index: last unmatched position
let best = 0;
for (let i = 0; i < s.length; i++) {
if (s[i] === '(') {
stack.push(i); // remember this opener's index
} else {
stack.pop(); // try to close the most recent opener
if (stack.length === 0) {
stack.push(i); // no base left → i is the new base
} else {
best = Math.max(best, i - stack[stack.length - 1]);
}
}
}
return best;
}[-1]. That -1 is a sentinel index sitting just before any possible valid run, so the first valid span measures correctly. best tracks the longest length seen.( we push its index i. Storing the index (not the char) is what lets us later compute a length by subtraction.) we always pop. If the stack becomes empty, this ) had no opener to match: we push i to make it the new base for future spans.stack[stack.length - 1] up to i is valid, so the run length is i - stack[stack.length - 1]; we keep the max in best.best holds the length of the longest valid parentheses substring.n characters; each index is pushed and popped at most once, so all stack work is amortized O(1). Total O(n) time and O(n) space for the stack. The two-pass counter variant achieves the same time in O(1) space.open/close; when equal record 2·close, when close > open reset both to 0. Then mirror it right-to-left (reset when open > close) to catch unbalanced-left runs like "(()".dp[i] = longest valid run ending at i. Only ) can extend a run; combine with dp[i-1] and the character before that matched opener. O(n) time, O(n) space.i − (−1). Without it you'd special-case the empty stack on every match.i where each new best occurs; the span is [top + 1, i] at that moment.) does here.function longestValidParentheses(s: string): number {
const stack: number[] = [-1]; // base index: last unmatched position
let best = 0;
for (let i = 0; i < s.length; i++) {
if (s[i] === '(') {
stack.push(i); // remember this opener's index
} else {
stack.pop(); // try to close the most recent opener
if (stack.length === 0) {
stack.push(i); // no base left → i is the new base
} else {
best = Math.max(best, i - stack[stack.length - 1]);
}
}
}
return best;
}n characters; each index is pushed and popped at most once, so all stack work is amortized O(1). Total O(n) time and O(n) space for the stack. The two-pass counter variant achieves the same time in O(1) space.Build a dp array where dp[i] is the length of the longest valid substring ending exactly at i, extending earlier runs by reaching back over them.
function longestValidParentheses(s: string): number {
const n = s.length;
const dp: number[] = new Array(n).fill(0); // dp[i] = longest valid run ending at i
let best = 0;
for (let i = 1; i < n; i++) {
if (s[i] !== ')') continue; // only a ')' can close a run
if (s[i - 1] === '(') {
// "...()" — pair up with the immediately preceding '('
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
} else {
// "...))" — reach back over the inner valid run dp[i-1]
const open = i - dp[i - 1] - 1; // index that must hold the matching '('
if (open >= 0 && s[open] === '(') {
dp[i] = dp[i - 1] + 2 + (open >= 1 ? dp[open - 1] : 0);
}
}
best = Math.max(best, dp[i]);
}
return best;
}s[i-1] is also a ), the matching opener sits just before the inner run, at i − dp[i-1] − 1; we then also tack on any valid run that ended just before that opener (dp[open-1]) so adjacent groups merge.Sweep once left-to-right and once right-to-left with two integer counters, recording a span whenever they balance and resetting whenever they go irrecoverably out of balance.
function longestValidParentheses(s: string): number {
let best = 0;
// Left-to-right: reset when ')' overtakes '(' (unmatched closer).
let open = 0;
let close = 0;
for (let i = 0; i < s.length; i++) {
if (s[i] === '(') open++;
else close++;
if (open === close) best = Math.max(best, 2 * close);
else if (close > open) open = close = 0;
}
// Right-to-left: reset when '(' overtakes ')' to catch runs like "(()".
open = 0;
close = 0;
for (let i = s.length - 1; i >= 0; i--) {
if (s[i] === '(') open++;
else close++;
if (open === close) best = Math.max(best, 2 * open);
else if (open > close) open = close = 0;
}
return best;
}"(()" where close never overtakes open; mirroring the scan from the right (resetting when open > close) recovers those runs. No extra data structure beyond two counters.Enumerate every even-length substring and test each for balance with a running counter — the baseline before any stack or DP insight.
function longestValidParentheses(s: string): number {
const n = s.length;
let best = 0;
for (let i = 0; i < n; i++) {
// Valid substrings have even length, so step j by 2.
for (let j = i + 1; j < n; j += 2) {
if (isValid(s, i, j)) {
best = Math.max(best, j - i + 1);
}
}
}
return best;
}
function isValid(s: string, lo: number, hi: number): boolean {
let balance = 0;
for (let k = lo; k <= hi; k++) {
balance += s[k] === '(' ? 1 : -1;
if (balance < 0) return false; // a ')' with no opener before it
}
return balance === 0;
}| "longest valid / well-formed substring" | stack of indices + base sentinel |
| need a length/span, not a yes/no | store indices, subtract i − top |
| stray closer breaks the run | push i as the new base |
| O(1) space required | two-pass open/close counters |
function longestValidParentheses(s: string): number {
const stack: number[] = [-1]; // sentinel base
let best = 0;
for (let i = 0; i < s.length; i++) {
if (s[i] === '(') {
stack.push(i);
} else {
stack.pop();
if (stack.length === 0) stack.push(i); // new base
else best = Math.max(best, i - stack[stack.length - 1]);
}
}
return best;
}")()())" return?-1?), after popping you find the stack is empty. What do you do?"(()", what is the answer?