diff --git a/Algorithm/BinarySearch/leetcode/20. Valid Parentheses/claude sonnet 5/README.md b/Algorithm/BinarySearch/leetcode/20. Valid Parentheses/claude sonnet 5/README.md new file mode 100644 index 00000000..eb7ff4ca --- /dev/null +++ b/Algorithm/BinarySearch/leetcode/20. Valid Parentheses/claude sonnet 5/README.md @@ -0,0 +1,1028 @@ +# Valid Parentheses - 括弧の対応関係をスタックで判定する(Python / TypeScript / Go / Rust) + +## 目次 + +- [概要](#overview) +- [アルゴリズム要点(TL;DR)](#tldr) +- [図解](#figures) +- [正しさのスケッチ](#correctness) +- [計算量](#complexity) +- [Python 実装](#impl-python) +- [TypeScript 実装](#impl-typescript) +- [Go 実装](#impl-go) +- [Rust 実装](#impl-rust) +- [言語間比較](#comparison) +- [言語別最適化ポイント](#optimization) +- [エッジケースと検証観点](#edgecases) +- [FAQ](#faq) + +--- + +
+ スタックによる括弧対応判定 による O(n) 実装(Python / TypeScript / Go / Rust) +
+ ++ 💡 + 一言で言うと:「開き括弧と閉じ括弧の対応関係と順序が正しいかどうかを判定する問題」 +
+ + ++ 💡 + この問題を一言で言うと:「開き括弧と閉じ括弧の対応関係と順序が正しいかどうかを判定する問題」です。 +
+
+ '('、')'、'{'、'}'、'['、']'
+ だけからなる文字列
+ s
+ が与えられます。すべての開き括弧が正しい種類・正しい順序で閉じられているなら
+ true、そうでなければ
+ false
+ を返します。
+
+ ⚠️ なぜ単純な方法では解けないのか +
+"([)]")を見逃してしまいます。
+ 📥 入出力例
+
+ ここでは特定の言語に依存せず、「文字列を読みながらスタックを操作する」という処理の流れを、
+ 代表例
+ s = "([)]"
+ を使って1ステップずつ確認します。
+ この考え方は、後の「コード(4言語)」セクションでPython / TypeScript / Go /
+ Rustのどの実装にも共通する土台になります。
+
+ 4言語とも「開き括弧が来たら対応する閉じ括弧をスタックに積み、閉じ括弧が来たらスタック最上部と直接比較する」という同じアルゴリズムを採用しています。 + タブを切り替えて、それぞれの言語らしい書き方・エラーの扱い方の違いを見比べてみましょう。 +
+ +📖 Big-O 記法の読み方
+| 言語 | ++ 採用アルゴリズム + | +時間計算量 | +空間計算量 | +実装上の特徴 | +
|---|---|---|---|---|
| Python | +スタック法 | +O(n) | +O(n) | +
+ list
+ をスタックに、dict
+ で対応表引き
+ |
+
| + TypeScript + | +スタック法 | +O(n) | +O(n) | +
+ string[]
+ をスタックに、readonly
+ な対応表
+ |
+
| Go | +スタック法 | +O(n) | +O(n) | +
+ []byte
+ をプリアロケーション、switch
+ で分岐
+ |
+
| Rust | +スタック法 | +O(n) | +O(n) | +
+ Vec<char>
+ を事前確保、Option<char>
+ で安全に比較
+ |
+
+ 🔍 なぜこの計算量になるのか +
+
+ 文字列
+ s
+ の各文字を1回ずつしか読まないため、時間計算量は文字列長 n に比例する O(n)
+ になります。
+ これより速くすることは、各文字を最低1回は確認する必要がある以上、理論上不可能です。
+ 空間計算量については、最悪ケース(全て開き括弧、例:"(((((")ではスタックに全文字が積まれるため O(n) になります。
+ 括弧の出現数だけを数えるO(1)空間のアプローチも存在しますが、順序の検証ができず不正解になるため採用していません。
+
+ 同じアルゴリズムでも、言語ごとに「メモリの扱い方」や「エラーの表現方法」が異なります。 + ここでは4言語の実装を横並びで比較し、それぞれの言語特有の設計判断を振り返ります。 +
+ +| 観点 | +Python | +TypeScript | +Go | +Rust | +
|---|---|---|---|---|
| + 採用アルゴリズム + | +スタック法 | +スタック法 | +スタック法 | +スタック法 | +
| + スタックの実体 + | +list[str] | +string[] | +[]byte | ++ Vec<char> + | +
| + メモリ確保の特徴 + | +動的拡張(償却O(1)) | ++ 動的拡張(V8内部最適化) + | ++ makeでプリアロケーションし再アロケーション回避 + | ++ with_capacityで事前確保し再アロケーション回避 + | +
| + エラー表現の方法 + | +例外(Exception) | +undefinedの型レベル表現 | ++ パニックを避ける範囲チェック + | ++ Option<T>による安全な比較 + | +
| + メモリ管理方式 + | ++ GC(参照カウント+世代別GC) + | +GC(V8のGC) | +GC(並行GC) | ++ 所有権システム(GC無し) + | +
| + 型安全性の担保方法 + | ++ pylance静的解析+実行時isinstance + | ++ コンパイル時の構造的型付け+型ガード + | +静的型付け+go vet | ++ コンパイル時の所有権チェック + | +
相違点の背景
+
+ Python・TypeScript・GoはいずれもGC(ガベージコレクション、=使い終わったメモリを自動で回収する仕組み)を持つ言語であり、
+ メモリの解放タイミングを意識する必要がありません。一方Rustは所有権システム(=コンパイル時にメモリの管理者を1つに定める仕組み)
+ でGC無しにメモリ安全性を実現しており、これがエラー表現の違いに最も強く表れています。
+ Python/TypeScript/Goが「例外」または「安全な値(undefinedやパニック回避チェック)」でエラーを扱うのに対し、
+ RustのOption<T>は「値があるかないか」を型として持つため、
+ 呼び出し側は必ずどちらのケースかを意識させられます。
+ 本問題はデータ構造・計算量の面で言語ごとの有利不利が生じるタイプの問題ではないため、4言語とも同じスタック法を採用していますが、
+ 対応表の型・エラーの扱い方には各言語のイディオム(=その言語で自然とされる書き方)がそれぞれ表れています。
+
+ このページで登場した専門用語をまとめました。分からない言葉が出てきたときに参照してください(五十音順)。 +
+strやTypeScript/JavaScriptの文字列はこの性質を持ちます。
+ typeofやin演算子などで実行時に値の型を確認し、その情報をもとにTypeScriptの型を絞り込む仕組みです。JavaScriptには無い、TypeScript独自のコンパイル時チェック機能です。
+ &T(読み取り専用)と&mut T(書き込み可能)があります。
+ []byteのように書き、動的にサイズを変えられます。
+ list.appendの再確保コストはこの考え方で「ならすとO(1)」とみなされます。
+ make([]byte, 0, len(s))やRustのVec::with_capacity(s.len())のように書くことで、途中の再確保を防ぎ高速化できます。
+ O(n)なら「入力が2倍になると処理も約2倍になる」ことを意味します。
+ + 水平走査法(Horizontal Scanning)による O(S) 実装(S = 全文字列の合計文字数) +
+ ++ 一言で言うと:「複数の文字列を先頭から見比べて、全員に共通する最長の接頭辞を取り出す問題」 +
+ + ++ 💡 + この問題を一言で言うと:「複数の文字列を先頭から見比べて、全員に共通する最長の接頭辞を取り出す問題」です。 +
++ 文字列の配列 strs + が与えられたとき、すべての文字列に共通する最長の接頭辞(先頭部分)を返します。 + 共通する接頭辞が1文字も無ければ、空文字列 "" を返します。 +
++ ⚠️ なぜ単純な方法では解けないのか +
+入出力例
+例1
++ strs = [flower, flow, flight] +
+出力: fl
++ なぜ正解か:3つの文字列すべてが f と l + で始まっており、3文字目でそれぞれ o / o / i と分かれるため、 + 共通しているのは先頭2文字の fl までです。 +
+例2
++ strs = [dog, racecar, car] +
++ 出力: (空文字列) +
++ なぜ正解か:dog, racecar, car + はそもそも先頭の1文字目からすべて異なる(d, r, c)ため、 + 共通する接頭辞は1文字も存在しません。 +
++ 代表例 strs = [flower, flow, flight] + を使って、水平走査アルゴリズムがどのように動くかを1ステップずつ確認しましょう。 + 左のリストからステップを選ぶか、「再生」ボタンで自動的に進めることができます。 +
+ ++ 同じアルゴリズムでも、言語によって書き方や最適化の方向性が変わります。 + タブを切り替えて、4言語それぞれの実装・動作トレース・言語特有のポイントを見比べてみましょう。 +
+ +📖 Big-O 記法の読み方
+| 言語 | ++ 採用アルゴリズム + | +時間計算量 | +空間計算量 | +候補の縮め方 | +
|---|---|---|---|---|
| Python | +垂直走査(zip + set) | +O(S) | +O(1) | ++ 一致した文字をリストに集め join で結合 + | +
| + TypeScript + | +水平走査(startsWith) | +O(S) | +O(1) | ++ slice で新しい文字列に再代入 + | +
| Go | ++ 水平走査(strings.HasPrefix) + | +O(S) | +O(1) | ++ スライス式でコピーせず範囲だけ変更 + | +
| Rust | +水平走査(starts_with) | +O(S) | +O(1) | ++ usize の長さのみ更新し文字列は最後に1回だけ生成 + | +
+ 🔍 なぜこの計算量になるのか +
++ どの言語の実装も、最悪の場合で全文字列の全文字を最大1回ずつ調べる構造になっているため、時間計算量は + O(S)(Sは全文字列の合計文字数)です。 + 空間計算量については、候補の文字列(または候補の長さを表す整数)以外に追加のデータ構造を使わないため、出力用の文字列を除けば + O(1) になります。 +
++ 同じアルゴリズムでも、言語ごとに「メモリの扱い方」や「エラーの表現方法」が異なります。 + ここでは4言語の実装を横並びで比較し、それぞれの言語特有の設計判断を振り返ります。 +
+ +| 観点 | +Python | +TypeScript | +Go | +Rust | +
|---|---|---|---|---|
| + メモリ確保の特徴 + | ++ join で最後に1回だけ結合 + | ++ slice のたびに新しい文字列 + | +スライス式はコピーなし | ++ 整数更新のみ、最後に1回だけ確保 + | +
| + エラー表現 + | ++ TypeError / ValueError の例外 + | ++ TypeError / RangeError の例外 + | ++ error 戻り値(提出版は省略) + | ++ Result型が定石だが戻り値固定のため空文字列で表現 + | +
| + メモリ管理方式 + | ++ GC(ガベージコレクション) + | ++ GC(ガベージコレクション) + | ++ GC(ガベージコレクション) + | ++ 所有権システム(GC不要) + | +
| + 型安全性の担保方法 + | +型ヒント + pylance | +readonly + 型ガード | +明示的な型宣言 + go vet | ++ 所有権システム + コンパイラチェック + | +
相違点の背景
++ Python・TypeScript・GoはGC(ガベージコレクション、=使い終わったメモリを自動で回収する仕組み)付きの言語であるため、 + 文字列の縮小操作で新しいオブジェクトが生成されてもGCが後始末をしてくれます。 + 一方Rustにはガベージコレクタが無く、コンパイル時に所有権を追跡する仕組みがあるため、 + 「新しい文字列を作らず整数だけを更新する」という、より低レベルなメモリ効率を意識した設計が自然に選ばれます。 +
++ このページで登場した専門用語をまとめました。分からない言葉が出てきたときに参照してください。 +
+List[str]
+ のように書き、pylance(VSCodeの型チェッカー)が実行前に型の誤りを検出できるようにします。
+ &str
+ のように書くことで、Vec<String>
+ の所有権を保持したまま中身を安全に読み取れます。
+ s[:i]
+ や Rust の
+ &s[..n]
+ がこれにあたります。
+