Skip to content

Commit 9eb619c

Browse files
committed
leetcode 32. Longest Valid Parentheses スタック法
1 parent b7d7d1b commit 9eb619c

11 files changed

Lines changed: 1525 additions & 0 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// 以下は、指定された問題に対する **TypeScript (5.1) + Node.js 18.16.1** 向けの **DP(動的計画法)版の解法** です。クラスは使わず、**関数ベース**で記述し、**LeetCodeの効率**も意識してあります。
2+
3+
// ---
4+
5+
// ### ✅ アルゴリズム概要(DP法)
6+
7+
// * `dp[i]` を「`i` 番目の文字を終点とする最長の有効な括弧の長さ」と定義します。
8+
// * `s[i] === ')'` であり、`s[i - 1] === '('` の場合 → `dp[i] = dp[i - 2] + 2`
9+
// * `s[i] === ')'` であり、`s[i - 1] === ')'` の場合は、`s[i - dp[i - 1] - 1] === '('` を確認してペアを拡張
10+
11+
// ---
12+
13+
// ### ✅ TypeScript 実装
14+
15+
// ```typescript
16+
/**
17+
* 最長の有効な括弧部分文字列の長さを返す(DP法)
18+
*
19+
* @param s - '(' と ')' のみからなる文字列
20+
* @returns 最長の well-formed な部分文字列の長さ
21+
*/
22+
function longestValidParenthesesDP(s: string): number {
23+
const n: number = s.length;
24+
const dp: number[] = new Array(n).fill(0);
25+
let maxLength: number = 0;
26+
27+
for (let i = 1; i < n; i++) {
28+
if (s[i] === ')') {
29+
if (s[i - 1] === '(') {
30+
// 直前が '(', 対応ペアなので +2
31+
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
32+
} else if (i - dp[i - 1] - 1 >= 0 && s[i - dp[i - 1] - 1] === '(') {
33+
// 直前も ')', さらにその前に対応する '(' がある
34+
dp[i] = dp[i - 1] + 2;
35+
if (i - dp[i - 1] - 2 >= 0) {
36+
dp[i] += dp[i - dp[i - 1] - 2]; // 前の連結部分も足す
37+
}
38+
}
39+
maxLength = Math.max(maxLength, dp[i]);
40+
}
41+
}
42+
43+
return maxLength;
44+
}
45+
// ```
46+
47+
// ---
48+
49+
// ### ✅ 使用例
50+
51+
// ```typescript
52+
// console.log(longestValidParentheses("(()")); // 2
53+
// console.log(longestValidParentheses(")()())")); // 4
54+
// console.log(longestValidParentheses("")); // 0
55+
// console.log(longestValidParentheses("()(()")); // 2
56+
// console.log(longestValidParentheses("()()")); // 4
57+
// ```
58+
59+
// ---
60+
61+
// ### ✅ 時間・空間計算量
62+
63+
// | 指標 | 値 |
64+
// | ------- | --------------------- |
65+
// | 時間計算量 | O(n) |
66+
// | 空間計算量 | O(n) (dp 配列使用) |
67+
// | 実行時間目安 | 約 60ms〜80ms(LeetCode) |
68+
// | メモリ使用目安 | 約 45MB〜50MB(LeetCode) |
69+
70+
// ---
71+
72+
// DP法は**スタック法よりもややメモリ使用量が多め**ですが、括弧の構造を明示的に覚えて拡張していくため、構文解析に近い用途では有効です。
73+
74+
// ご希望があれば、**空間O(1)の両方向スキャン解法**も提供できます。
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# O(1) 追加メモリで最長有効括弧長を求める方法は、
2+
# **左右 2 回のスキャン法(two-pass scan method)** を使います。
3+
4+
# ---
5+
6+
# ## **アルゴリズムの概要**
7+
8+
# 1. **左→右スキャン**
9+
10+
# * `left` カウント: '(' の数
11+
# * `right` カウント: ')' の数
12+
# * `left == right` の時点で長さ `2 * right` が有効
13+
# * `right > left` になったらリセット(`(` より `)` が多くなったので無効)
14+
15+
# 2. **右→左スキャン**
16+
17+
# * `left` カウント: '(' の数
18+
# * `right` カウント: ')' の数
19+
# * 逆方向で同様にカウント
20+
# * 今度は `left > right` でリセット
21+
# * これにより `)` が先行する場合も検出可能
22+
23+
# ---
24+
25+
# ### **Python (CPython 3.11.4) 実装例**
26+
27+
# ```python
28+
class Solution:
29+
def longestValidParentheses(self, s: str) -> int:
30+
"""
31+
O(1) 追加メモリ版
32+
パラメータ:
33+
s (str): '(' と ')' のみからなる文字列
34+
戻り値:
35+
int: 最長の有効な括弧部分文字列の長さ
36+
37+
計算量:
38+
時間計算量 O(n)
39+
空間計算量 O(1) 追加メモリ
40+
"""
41+
max_len: int = 0
42+
left: int = 0
43+
right: int = 0
44+
45+
# 1. 左→右スキャン
46+
for ch in s:
47+
if ch == '(':
48+
left += 1
49+
else:
50+
right += 1
51+
52+
if left == right:
53+
max_len = max(max_len, 2 * right)
54+
elif right > left:
55+
left = right = 0
56+
57+
# 2. 右→左スキャン
58+
left = right = 0
59+
for ch in reversed(s):
60+
if ch == '(':
61+
left += 1
62+
else:
63+
right += 1
64+
65+
if left == right:
66+
max_len = max(max_len, 2 * left)
67+
elif left > right:
68+
left = right = 0
69+
70+
return max_len
71+
72+
73+
# 動作確認
74+
if __name__ == "__main__":
75+
solver = Solution()
76+
test_cases = ["(()", ")()())", "", "()(())", "())((())"]
77+
for s in test_cases:
78+
print(f"Input: {s!r}, Output: {solver.longestValidParentheses(s)}")
79+
80+
# ### **実行結果例**
81+
82+
# ```
83+
# Input: '(()', Output: 2
84+
# Input: ')()())', Output: 4
85+
# Input: '', Output: 0
86+
# Input: '()(())', Output: 6
87+
# Input: '())((())', Output: 4
88+
# ```
89+
90+
# ---
91+
92+
# ### **処理時間・メモリ消費量**
93+
94+
# * **時間計算量**:
95+
96+
# * 左右スキャンで **O(n)**
97+
98+
# * **空間計算量**:
99+
100+
# * カウンタ変数のみ → **O(1)**
101+
102+
# * **メモリ使用量目安**:
103+
104+
# * 追加メモリほぼゼロ(数個の int のみ)
105+
106+
# ---
107+
108+
# ### **メリット・デメリット**
109+
110+
# * ✅ メモリ効率が非常に良い(スタック不要)
111+
# * ✅ 2 パスなので比較的実装がシンプル
112+
# * ⚠️ 実際のインデックス位置を復元するのは難しい(長さのみ計算向け)
113+
114+
# ---
115+
116+
# 質問です。
117+
# この問題を **LeetCode で提出**する場合、あなたは
118+
119+
# 1. **O(1) メモリの最適版**を提出して高速化したい
120+
# 2. **O(n) スタック法**のほうがバグ少なく安心
121+
122+
# どちらを優先したいですか?
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
function longestValidParenthesesO1(s: string): number {
2+
let left = 0;
3+
let right = 0;
4+
let maxLen = 0;
5+
6+
// 左→右
7+
for (const ch of s) {
8+
if (ch === '(') left++;
9+
else right++;
10+
if (left === right) maxLen = Math.max(maxLen, 2 * right);
11+
else if (right > left) left = right = 0;
12+
}
13+
14+
left = 0;
15+
right = 0;
16+
17+
// 右→左
18+
for (let i = s.length - 1; i >= 0; i--) {
19+
const ch = s[i];
20+
if (ch === '(') left++;
21+
else right++;
22+
if (left === right) maxLen = Math.max(maxLen, 2 * left);
23+
else if (left > right) left = right = 0;
24+
}
25+
26+
return maxLen;
27+
}
28+
29+
// 動作確認
30+
// const testCases = [')()())', '(()', '()(())', '())((())'];
31+
for (const s of testCases) {
32+
console.log(`Input: '${s}', Output: ${longestValidParenthesesO1(s)}`);
33+
}

0 commit comments

Comments
 (0)