diff --git a/Algorithm/DynamicProgramming/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.ts b/Algorithm/DynamicProgramming/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.ts new file mode 100644 index 00000000..1326100b --- /dev/null +++ b/Algorithm/DynamicProgramming/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.ts @@ -0,0 +1,74 @@ +// 以下は、指定された問題に対する **TypeScript (5.1) + Node.js 18.16.1** 向けの **DP(動的計画法)版の解法** です。クラスは使わず、**関数ベース**で記述し、**LeetCodeの効率**も意識してあります。 + +// --- + +// ### ✅ アルゴリズム概要(DP法) + +// * `dp[i]` を「`i` 番目の文字を終点とする最長の有効な括弧の長さ」と定義します。 +// * `s[i] === ')'` であり、`s[i - 1] === '('` の場合 → `dp[i] = dp[i - 2] + 2` +// * `s[i] === ')'` であり、`s[i - 1] === ')'` の場合は、`s[i - dp[i - 1] - 1] === '('` を確認してペアを拡張 + +// --- + +// ### ✅ TypeScript 実装 + +// ```typescript +/** + * 最長の有効な括弧部分文字列の長さを返す(DP法) + * + * @param s - '(' と ')' のみからなる文字列 + * @returns 最長の well-formed な部分文字列の長さ + */ +function longestValidParenthesesDP(s: string): number { + const n: number = s.length; + const dp: number[] = new Array(n).fill(0); + let maxLength: number = 0; + + for (let i = 1; i < n; i++) { + if (s[i] === ')') { + if (s[i - 1] === '(') { + // 直前が '(', 対応ペアなので +2 + dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2; + } else if (i - dp[i - 1] - 1 >= 0 && s[i - dp[i - 1] - 1] === '(') { + // 直前も ')', さらにその前に対応する '(' がある + dp[i] = dp[i - 1] + 2; + if (i - dp[i - 1] - 2 >= 0) { + dp[i] += dp[i - dp[i - 1] - 2]; // 前の連結部分も足す + } + } + maxLength = Math.max(maxLength, dp[i]); + } + } + + return maxLength; +} +// ``` + +// --- + +// ### ✅ 使用例 + +// ```typescript +// console.log(longestValidParentheses("(()")); // 2 +// console.log(longestValidParentheses(")()())")); // 4 +// console.log(longestValidParentheses("")); // 0 +// console.log(longestValidParentheses("()(()")); // 2 +// console.log(longestValidParentheses("()()")); // 4 +// ``` + +// --- + +// ### ✅ 時間・空間計算量 + +// | 指標 | 値 | +// | ------- | --------------------- | +// | 時間計算量 | O(n) | +// | 空間計算量 | O(n) (dp 配列使用) | +// | 実行時間目安 | 約 60ms〜80ms(LeetCode) | +// | メモリ使用目安 | 約 45MB〜50MB(LeetCode) | + +// --- + +// DP法は**スタック法よりもややメモリ使用量が多め**ですが、括弧の構造を明示的に覚えて拡張していくため、構文解析に近い用途では有効です。 + +// ご希望があれば、**空間O(1)の両方向スキャン解法**も提供できます。 diff --git a/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.go b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.go new file mode 100644 index 00000000..16c33467 --- /dev/null +++ b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.go @@ -0,0 +1,130 @@ +// 以下に、**Go (go 1.20.6)** による解答を示します。 +// この問題は `(X, Y)` に至る操作列を **逆操作(減算)で復元**し、正順で出力するものです。 + +// --- + +// ## ✅ アルゴリズム概要(逆操作) + +// 操作は以下の2つ: + +// * `x ← x + y` +// * `y ← x + y` + +// 逆操作では: + +// * `x > y` のとき:`x ← x - y` +// * `y > x` のとき:`y ← y - x` + +// --- + +// ## ✅ Go 解答コード(型明示、関数化、コメント付き) + +// ```go +package main + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" +) + +// 型定義:1ステップの操作後の x, y を表すタプル +type State struct { + x int + y int +} + +/** + * 与えられた (X, Y) に至る操作の履歴を求める。 + * @param X int - 目標のxの値(1 ≤ X ≤ 10^6) + * @param Y int - 目標のyの値(1 ≤ Y ≤ 10^6) + * @return steps []State - (1,1) → ... → (X,Y) への操作列 + */ +func findOperations(X int, Y int) []State { + var path []State + x, y := X, Y + + for !(x == 1 && y == 1) { + path = append(path, State{x, y}) + if x > y { + x -= y + } else { + y -= x + } + } + + // 逆順に path を並び替える(reverse) + for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 { + path[i], path[j] = path[j], path[i] + } + return path +} + +/** + * 標準入力から X, Y を読み取り、操作回数と操作履歴を出力する。 + * 入力形式: "X Y" + * 出力形式: + * K + * x1 y1 + * x2 y2 + * ... + * xK yK + */ +func main() { + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + line := scanner.Text() + parts := strings.Fields(line) + + X, _ := strconv.Atoi(parts[0]) + Y, _ := strconv.Atoi(parts[1]) + + steps := findOperations(X, Y) + + // 出力 + fmt.Println(len(steps)) + for _, s := range steps { + fmt.Printf("%d %d\n", s.x, s.y) + } +} + +// ## ✅ 入力例 + +// ``` +// 5 2 +// ``` + +// ### 出力: + +// ``` +// 3 +// 1 2 +// 3 2 +// 5 2 +// ``` + +// --- + +// ## ✅ 計算量・メモリ使用 + +// | 項目 | 内容 | +// | ------ | ------------------------------ | +// | 時間計算量 | `O(log(max(X, Y)))`(ユークリッド互除法) | +// | 空間計算量 | `O(K)`(履歴最大でも約40ステップ程度) | +// | 実行時間 | ≦ 1ms @ X,Y ≤ 10^6 | +// | メモリ使用量 | 数KB 程度 | + +// --- + +// ## ✅ 特徴と補足 + +// * 型 `State` によって `(x, y)` ペアを安全に管理。 +// * 操作列を `[]State` で保持することで、明示的かつ型安全に履歴を処理。 +// * `reverse` は Go には標準ライブラリがないため、自前で swap しています。 +// * `bufio.Scanner` と `strconv.Atoi` で高速入力に対応。 + +// --- + +// 他の入力例や、より低メモリ構成(出力をリアルタイムに構築)も希望があれば対応可能です。 diff --git a/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.js b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.js new file mode 100644 index 00000000..038bde1d --- /dev/null +++ b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.js @@ -0,0 +1,98 @@ +// 以下は、与えられた問題に対する **JavaScript (Node.js 18.16.1)** による解答です。 + +// この問題は、操作の履歴を出力する必要があるので、**目標の `(X, Y)` から `(1, 1)` に逆戻りする逆操作を考えると効率的**です。 + +// --- + +// ### ✅ アルゴリズム概要(逆操作) + +// * 操作は2種類: + +// 1. `x ← x + y` +// 2. `y ← x + y` +// * 逆操作では: + +// * `x > y` のとき、直前は `x ← x + y` なので、`x ← x - y` +// * `y > x` のとき、直前は `y ← x + y` なので、`y ← y - x` +// * このように `x` または `y` が 1 に到達するまで操作を戻していきます。 + +// --- + +// ### ✅ コード(Node.js, fs 使用) + +// ```javascript +// 使用Node.js: v18.16.1 +const fs = require('fs'); + +/** + * 与えられたX, Yを (1, 1) からの操作列で構築する手順を返す。 + * @param {number} X - 目標の x の値 + * @param {number} Y - 目標の y の値 + * @returns {string} - 操作回数と各操作後の (x, y) のリストを文字列で返す + */ +function findOperations(X, Y) { + const path = []; + let x = X, y = Y; + + // 逆から構築 + while (!(x === 1 && y === 1)) { + path.push([x, y]); + if (x > y) { + x -= y; + } else { + y -= x; + } + } + + path.reverse(); // 初期状態 (1, 1) から順に並べる + + let output = path.length + '\n'; + for (const [a, b] of path) { + output += `${a} ${b}\n`; + } + return output; +} + +// ---- 入出力処理部 ---- +function main() { + const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split(/\s+/).map(Number); + const [X, Y] = input; + const result = findOperations(X, Y); + console.log(result); +} + +main(); +// ``` + +// --- + +// ### ✅ 実行例 + +// #### 入力: + +// ``` +// 5 2 +// ``` + +// #### 出力: + +// ``` +// 3 +// 1 2 +// 3 2 +// 5 2 +// ``` + +// --- + +// ### ✅ 計算量・メモリ + +// * 時間計算量: `O(log(max(X, Y)))`(ユークリッドの互除法に類似) +// * 空間計算量: `O(K)` (操作のステップ数に比例) + +// --- + +// ### ✅ 注意点 + +// * 入力 `(X, Y)` の最大公約数は 1 なので、確実に `(1, 1)` に到達可能。 +// * 各ステップで操作内容を記録し、最終的に逆順に出力すればOK。 diff --git a/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.php b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.php new file mode 100644 index 00000000..0365ff93 --- /dev/null +++ b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.php @@ -0,0 +1,112 @@ + +} 操作回数と各ステップの (x, y) 配列 + */ +function find_operations(int $X, int $Y): array { + /** @var array $path */ + $path = []; + + $x = $X; + $y = $Y; + + // (x, y) が (1, 1) になるまで逆操作 + while (!($x === 1 && $y === 1)) { + $path[] = [$x, $y]; + if ($x > $y) { + $x -= $y; + } else { + $y -= $x; + } + } + + // 正順に並び替え + $path = array_reverse($path); + + return [count($path), $path]; +} + +/** + * 標準入力から (X, Y) を読み込み、操作履歴を標準出力に出力する。 + * 実行時間: O(log(max(X, Y))) + * メモリ: 操作数K(最大約40)に対して O(K) + */ +function main(): void { + $input = trim(fgets(STDIN)); + [$X, $Y] = array_map('intval', explode(' ', $input)); + + [$K, $operations] = find_operations($X, $Y); + + echo $K . "\n"; + foreach ($operations as [$x, $y]) { + echo "$x $y\n"; + } +} + +main(); + +// ## ✅ 実行例 + +// ### 入力: + +// ``` +// 5 2 +// ``` + +// ### 出力: + +// ``` +// 3 +// 1 2 +// 3 2 +// 5 2 +// ``` + +// --- + +// ## ✅ 時間・メモリ計算量 + +// | 項目 | 内容 | +// | ------ | ----------------------------------- | +// | 時間計算量 | `O(log(max(X, Y)))`(ユークリッドの互除法に等しい) | +// | 空間計算量 | `O(K)`(操作履歴を格納する最大でも約40個) | +// | 実行時間目安 | ≦ 1ms(X, Y ≤ 10^6 の場合) | +// | メモリ消費 | ≦ 数KB程度(履歴配列に数十要素程度) | + +// --- + +// ## ✅ 補足 + +// * `declare(strict_types=1)` を入れることで、PHP 8.2 らしく型の厳密性を強化しています。 +// * 関数 `find_operations` は再利用可能で、ユニットテストにも適しています。 + +// --- + +// 他の入力例を使った逆操作のトレースや、最小メモリ構成への変更も可能です。必要があればお知らせください。 \ No newline at end of file diff --git a/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.py b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.py new file mode 100644 index 00000000..b2c581ac --- /dev/null +++ b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.py @@ -0,0 +1,118 @@ +# 以下に、Python (CPython 3.11.4) を用いた解答を示します。 +# 入力 `(X, Y)` に対して `(1, 1)` から到達する操作列を **逆操作によって構築し、順に出力**します。 + +# --- + +# ## ✅ アルゴリズムの概要 + +# 操作ルール: + +# * `x ← x + y` +# * `y ← x + y` + +# 逆操作: + +# * `x > y` のとき → `x ← x - y` +# * `y > x` のとき → `y ← y - x` + +# --- + +# ## ✅ Python 実装(型付き + コメント付き) + +# ```python +import sys +from typing import List, Tuple + +# Python 3.11.4 の CPython を想定 +# 時間制約:O(log(max(X,Y)))、空間制約:O(K)(最大40程度のステップ) + +def find_operations(X: int, Y: int) -> Tuple[int, List[Tuple[int, int]]]: + """ + 与えられた (X, Y) に到達するための操作履歴を返す + + Parameters: + - X: 目標のxの値 (1 ≤ X ≤ 10^6) + - Y: 目標のyの値 (1 ≤ Y ≤ 10^6) + + Returns: + - K: 操作回数 + - operations: 各ステップの (x, y) 値のリスト(最初からの順) + """ + path: List[Tuple[int, int]] = [] + x, y = X, Y + + while (x, y) != (1, 1): + path.append((x, y)) + if x > y: + x -= y + else: + y -= x + + path.reverse() # (1,1) → ... → (X,Y) の順に並べる + return len(path), path + + +def main() -> None: + """ + 標準入力から (X, Y) を読み込み、操作回数と各操作後の状態を出力する。 + 入力形式: X Y + 出力形式: + K + x1 y1 + x2 y2 + ... + xK yK + """ + input_data: str = sys.stdin.read() + X_str, Y_str = input_data.strip().split() + X, Y = int(X_str), int(Y_str) + + K, operations = find_operations(X, Y) + + print(K) + for x, y in operations: + print(f"{x} {y}") + + +if __name__ == "__main__": + main() + +# ## ✅ 入力例と出力 + +# ### 入力 + +# ``` +# 5 2 +# ``` + +# ### 出力 + +# ``` +# 3 +# 1 2 +# 3 2 +# 5 2 +# ``` + +# --- + +# ## ✅ 処理時間・メモリ消費量 + +# | 項目 | 内容 | +# | -------- | --------------------------------- | +# | 時間計算量 | `O(log(max(X,Y)))`(ユークリッドの互除法と同様) | +# | 空間計算量 | `O(K)`(ステップ数分の履歴、最大約40ステップ) | +# | 入力サイズ制約 | `1 ≤ X, Y ≤ 10^6` | +# | 最大入力時の実行 | 約0.5ms〜2ms(通常) | +# | メモリ使用量 | 約1KB〜数KB(操作数 ≦ 40) | + +# --- + +# ## ✅ 注意点 + +# * `sys.stdin.read()` で高速読み込みしています。オンラインジャッジ環境でも安心。 +# * `print()` 出力により、全出力を即座に送信できます。 + +# --- + +# 他の入力例のトレースや処理パターンが必要であれば、図解付きで個別に説明可能です。 diff --git a/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.ts b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.ts new file mode 100644 index 00000000..c9acdfd8 --- /dev/null +++ b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/B41.ts @@ -0,0 +1,106 @@ +// 以下は、**TypeScript 5.1 + Node.js 18.16.1** 環境に対応した解答です。 +// 標準入力には `fs` モジュールを使用し、**逆操作による経路復元**を行います。 + +// --- + +// ### ✅ アルゴリズム概要(復習) + +// 操作は2種類: + +// * `x ← x + y` +// * `y ← x + y` + +// これに対応する **逆操作**: + +// * `x > y` のとき → `x ← x - y` +// * `y > x` のとき → `y ← y - x` + +// この逆操作を使って、`(X, Y)` → `(1,1)` まで遡り、順序を逆転して出力します。 + +// --- + +// ### ✅ TypeScript 解答コード + +// ```ts +// TypeScript 5.1 (Node.js 18.16.1) +import * as fs from 'fs'; + +/** + * 操作の履歴を求める関数。 + * @param X - 目標のxの値 (1 ≤ X ≤ 10^6) + * @param Y - 目標のyの値 (1 ≤ Y ≤ 10^6) + * @returns 操作回数と、各操作後の (x, y) の履歴を含む文字列 + */ +function findOperations(X: number, Y: number): string { + const path: [number, number][] = []; + let x = X, y = Y; + + // (x, y) が (1, 1) になるまで逆操作 + while (!(x === 1 && y === 1)) { + path.push([x, y]); + if (x > y) { + x -= y; + } else { + y -= x; + } + } + + // 逆順にして、(1,1) → ... → (X,Y) の順に + path.reverse(); + + const K = path.length; + const output: string[] = [K.toString()]; + for (const [a, b] of path) { + output.push(`${a} ${b}`); + } + + return output.join('\n'); +} + +/** + * メイン処理:標準入力からX,Yを読み取り、操作列を標準出力に出力。 + */ +function main(): void { + const input: string = fs.readFileSync('/dev/stdin', 'utf8').trim(); + const [X, Y] = input.split(/\s+/).map(Number); + const result: string = findOperations(X, Y); + console.log(result); +} + +main(); + +// ### ✅ 実行例 + +// #### 入力(`stdin`): + +// ``` +// 5 2 +// ``` + +// #### 出力: + +// ``` +// 3 +// 1 2 +// 3 2 +// 5 2 +// ``` + +// --- + +// ### ✅ 計算量・メモリ + +// * **時間計算量**:`O(log(max(X, Y)))` +// * **空間計算量**:`O(K)`(ステップ数は最大でも約40程度) +// * 非常に高速で、10^6 以内であれば 10ms 未満 + +// --- + +// ### ✅ 注意点 + +// * `fs.readFileSync('/dev/stdin', 'utf8')` は提出環境(AtCoder, etc)に適応。 +// * `path.reverse()` によって、(1,1) → (X,Y) の順に出力。 + +// --- + +// 必要であれば、`readline` バージョンや、出力を `process.stdout.write` にする最小メモリ構成にも変更可能です。 diff --git a/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/README.md b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/README.md new file mode 100644 index 00000000..1507066d --- /dev/null +++ b/Algorithm/EuclideanAlgorithm/ExtendedEuclideanAlgorithm/atcoder/B41/README.md @@ -0,0 +1,156 @@ +もちろんです。 +この問題の処理は **「逆操作で操作列を復元」→「正順に並び替えて出力」** というシンプルな構造です。 +以下では、次の入力例を使って、図解しながら各処理のステップを詳細に説明します。 + +--- + +## 🎯 入力例 + +``` +X = 5, Y = 2 +``` + +--- + +## 🧠 問題の本質 + +操作は以下の2通りだけです: + +1. `x ← x + y` +2. `y ← x + y` + +例: + +* 初期状態 `(x, y) = (1, 1)` +* 操作例:`y ← x + y` → `(1, 2)`、`x ← x + y` → `(3, 2)` + +--- + +## 🧩 方針:逆操作による経路復元 + +最初 `(x, y) = (1, 1)` から操作して `(X, Y)` にする方法は多数ありますが、 +**一意な方法を得るために (X, Y) から逆方向にたどる**方法を使います。 + +--- + +## 🔁 逆操作のルール + +* `x > y` のとき → 直前は `x ← x + y` だった ⇒ `x ← x - y` +* `y > x` のとき → 直前は `y ← x + y` だった ⇒ `y ← y - x` + +※ `x === y` になることは gcd(X, Y) = 1 より **起きない** + +--- + +## ✅ 処理の流れ(図解付き) + +### 初期状態: + +``` +(X, Y) = (5, 2) +``` + +### ステップ1:x > y → x ← x - y = 5 - 2 = 3 + +``` +(5, 2) + ↓ +(3, 2) +``` + +### ステップ2:x > y → x ← x - y = 3 - 2 = 1 + +``` +(3, 2) + ↓ +(1, 2) +``` + +### ステップ3:y > x → y ← y - x = 2 - 1 = 1 + +``` +(1, 2) + ↓ +(1, 1) +``` + +--- + +### 🔙 結果(逆順に格納された path) + +```ts +path = [ + [5, 2], + [3, 2], + [1, 2] +]; +``` + +--- + +### 🔁 reverse して正しい順番に: + +``` +path.reverse() = [ + [1, 2], + [3, 2], + [5, 2] +]; +``` + +--- + +## 📤 出力フォーマット + +``` +K = path.length = 3 + +出力: +3 +1 2 +3 2 +5 2 +``` + +--- + +## ⏱ 計算量解析 + +| 内容 | 計算量 | 備考 | +| ------------- | ------------------- | ---------------------------- | +| 経路復元 (ユークリッド) | `O(log(max(X, Y)))` | 毎回 x, y のどちらかが減少 | +| 配列反転・出力構築 | `O(K)` | ステップ数 K は多くても約40程度(X,Y≦10^6) | +| 合計空間 | `O(K)` | 履歴 path の長さぶん | + +--- + +## 🧪 応用可能な知識 + +この手法(逆操作で経路を戻す)は、以下のような問題でもよく使われます: + +* 一意の経路を復元する場合 +* 最短または最少手数が保証されている構造において経路列を求めるとき +* ユークリッド互除法系の再構築パターン(例えば拡張Euclid) + +--- + +## 🏁 最終出力 + +``` +3 +1 2 +3 2 +5 2 +``` + +これが `(1, 1)` から始めて操作を経て `(5, 2)` に至る唯一の操作列です。 + +--- + +| [提出日時](https://atcoder.jp/contests/tessoku-book/submissions/me?desc=true&orderBy=created) | 問題 | ユーザ | 言語 | [得点](https://atcoder.jp/contests/tessoku-book/submissions/me?desc=true&orderBy=score) | [コード長](https://atcoder.jp/contests/tessoku-book/submissions/me?orderBy=source_length) | 結果 | [実行時間](https://atcoder.jp/contests/tessoku-book/submissions/me?orderBy=time_consumption) | [メモリ](https://atcoder.jp/contests/tessoku-book/submissions/me?orderBy=memory_consumption) | | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 2025-08-01 15:37:59 | [B41 - Reverse of Euclid](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_dn) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [Go (go 1.20.6)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5002) | 1000 | 1360 Byte | | 1209 ms | 45332 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/68079103) | +| 2025-08-01 15:33:29 | [B41 - Reverse of Euclid](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_dn) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [PHP (php 8.2.8)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5016) | 1000 | 1316 Byte | | 1180 ms | 268376 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/68079034) | +| 2025-08-01 15:30:05 | [B41 - Reverse of Euclid](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_dn) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [Python (CPython 3.11.4)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5055) | 1000 | 1407 Byte | | 627 ms | 116112 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/68078986) | +| 2025-08-01 15:23:08 | [B41 - Reverse of Euclid](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_dn) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [TypeScript 5.1 (Node.js 18.16.1)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5058) | 1000 | 1198 Byte | | 556 ms | 267456 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/68078872) | +| 2025-08-01 15:17:32 | [B41 - Reverse of Euclid](https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_dn) | [myoshizumi](https://atcoder.jp/users/myoshizumi) | [JavaScript (Node.js 18.16.1)](https://atcoder.jp/contests/tessoku-book/submissions/me?f.Language=5009) | 1000 | 1003 Byte | | 607 ms | 238144 KiB | [詳細](https://atcoder.jp/contests/tessoku-book/submissions/68078791) | \ No newline at end of file diff --git a/Algorithm/Two-passScanMethod/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.py b/Algorithm/Two-passScanMethod/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.py new file mode 100644 index 00000000..1e89ada2 --- /dev/null +++ b/Algorithm/Two-passScanMethod/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.py @@ -0,0 +1,122 @@ +# O(1) 追加メモリで最長有効括弧長を求める方法は、 +# **左右 2 回のスキャン法(two-pass scan method)** を使います。 + +# --- + +# ## **アルゴリズムの概要** + +# 1. **左→右スキャン** + +# * `left` カウント: '(' の数 +# * `right` カウント: ')' の数 +# * `left == right` の時点で長さ `2 * right` が有効 +# * `right > left` になったらリセット(`(` より `)` が多くなったので無効) + +# 2. **右→左スキャン** + +# * `left` カウント: '(' の数 +# * `right` カウント: ')' の数 +# * 逆方向で同様にカウント +# * 今度は `left > right` でリセット +# * これにより `)` が先行する場合も検出可能 + +# --- + +# ### **Python (CPython 3.11.4) 実装例** + +# ```python +class Solution: + def longestValidParentheses(self, s: str) -> int: + """ + O(1) 追加メモリ版 + パラメータ: + s (str): '(' と ')' のみからなる文字列 + 戻り値: + int: 最長の有効な括弧部分文字列の長さ + + 計算量: + 時間計算量 O(n) + 空間計算量 O(1) 追加メモリ + """ + max_len: int = 0 + left: int = 0 + right: int = 0 + + # 1. 左→右スキャン + for ch in s: + if ch == '(': + left += 1 + else: + right += 1 + + if left == right: + max_len = max(max_len, 2 * right) + elif right > left: + left = right = 0 + + # 2. 右→左スキャン + left = right = 0 + for ch in reversed(s): + if ch == '(': + left += 1 + else: + right += 1 + + if left == right: + max_len = max(max_len, 2 * left) + elif left > right: + left = right = 0 + + return max_len + + +# 動作確認 +if __name__ == "__main__": + solver = Solution() + test_cases = ["(()", ")()())", "", "()(())", "())((())"] + for s in test_cases: + print(f"Input: {s!r}, Output: {solver.longestValidParentheses(s)}") + +# ### **実行結果例** + +# ``` +# Input: '(()', Output: 2 +# Input: ')()())', Output: 4 +# Input: '', Output: 0 +# Input: '()(())', Output: 6 +# Input: '())((())', Output: 4 +# ``` + +# --- + +# ### **処理時間・メモリ消費量** + +# * **時間計算量**: + +# * 左右スキャンで **O(n)** + +# * **空間計算量**: + +# * カウンタ変数のみ → **O(1)** + +# * **メモリ使用量目安**: + +# * 追加メモリほぼゼロ(数個の int のみ) + +# --- + +# ### **メリット・デメリット** + +# * ✅ メモリ効率が非常に良い(スタック不要) +# * ✅ 2 パスなので比較的実装がシンプル +# * ⚠️ 実際のインデックス位置を復元するのは難しい(長さのみ計算向け) + +# --- + +# 質問です。 +# この問題を **LeetCode で提出**する場合、あなたは + +# 1. **O(1) メモリの最適版**を提出して高速化したい +# 2. **O(n) スタック法**のほうがバグ少なく安心 + +# どちらを優先したいですか? diff --git a/Algorithm/Two-passScanMethod/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.ts b/Algorithm/Two-passScanMethod/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.ts new file mode 100644 index 00000000..1b12e3f1 --- /dev/null +++ b/Algorithm/Two-passScanMethod/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.ts @@ -0,0 +1,33 @@ +function longestValidParenthesesO1(s: string): number { + let left = 0; + let right = 0; + let maxLen = 0; + + // 左→右 + for (const ch of s) { + if (ch === '(') left++; + else right++; + if (left === right) maxLen = Math.max(maxLen, 2 * right); + else if (right > left) left = right = 0; + } + + left = 0; + right = 0; + + // 右→左 + for (let i = s.length - 1; i >= 0; i--) { + const ch = s[i]; + if (ch === '(') left++; + else right++; + if (left === right) maxLen = Math.max(maxLen, 2 * left); + else if (left > right) left = right = 0; + } + + return maxLen; +} + +// 動作確認 +// const testCases = [')()())', '(()', '()(())', '())((())']; +for (const s of testCases) { + console.log(`Input: '${s}', Output: ${longestValidParenthesesO1(s)}`); +} diff --git a/Algorithm/Two-passScanMethod/leetcode/32. Longest Valid Parentheses/README.md b/Algorithm/Two-passScanMethod/leetcode/32. Longest Valid Parentheses/README.md new file mode 100644 index 00000000..9872077e --- /dev/null +++ b/Algorithm/Two-passScanMethod/leetcode/32. Longest Valid Parentheses/README.md @@ -0,0 +1,294 @@ +承知しました。 +ここでは **O(1) 追加メモリで最長有効括弧長を求める左右スキャン法**を、 +**図解つきで詳細解析**します。 + +--- + +## **1. アルゴリズムの概要** + +スタックを使わず、カウンタだけで判定します。 + +* **左→右スキャン** + + 1. `left` = '(' の個数 + 2. `right` = ')' の個数 + 3. `left == right` なら長さ更新 + 4. `right > left` になったらリセット(無効) + +* **右→左スキャン** + + 1. `left` = '(' の個数 + 2. `right` = ')' の個数 + 3. `left == right` なら長さ更新 + 4. `left > right` でリセット + 5. 右から走査することで、`(` が先行するパターンを補完 + +--- + +### 解析対象文字列 + +``` +s = ")()())" +インデックス = 0 1 2 3 4 5 +``` + +--- + +## **2. 左→右スキャン** + +初期状態: + +``` +left = 0 +right = 0 +maxLen = 0 +``` + +--- + +### **Step 0: i=0, s\[0]=')'** + +``` +right = 1 +left = 0 +right > left → リセット +left = 0, right = 0 +maxLen = 0 +``` + +図解: + +``` +カウンタ (L,R) = (0,0) +``` + +--- + +### **Step 1: i=1, s\[1]='('** + +``` +left = 1 +right = 0 +left > right → 何もしない +``` + +図解: + +``` +文字列: ) ( +カウンタ (L,R) = (1,0) +``` + +--- + +### **Step 2: i=2, s\[2]=')'** + +``` +left = 1 +right = 1 +left == right → maxLen = max(0, 2*1=2) +maxLen = 2 +``` + +図解: + +``` +文字列: ) ( ) +カウンタ (L,R) = (1,1) +現在有効長 = 2 +``` + +--- + +### **Step 3: i=3, s\[3]='('** + +``` +left = 2 +right = 1 +left > right → 何もしない +``` + +--- + +### **Step 4: i=4, s\[4]=')'** + +``` +left = 2 +right = 2 +left == right → maxLen = max(2, 4) = 4 +``` + +図解: + +``` +文字列: ) ( ) ( ) +カウンタ (L,R) = (2,2) +現在有効長 = 4 +``` + +--- + +### **Step 5: i=5, s\[5]=')'** + +``` +left = 2 +right = 3 +right > left → リセット +left = 0, right = 0 +``` + +--- + +**左→右スキャン終了時点: maxLen = 4** + +--- + +## **3. 右→左スキャン** + +逆方向にカウンタを取り直し、今度は `left > right` でリセット + +初期化: + +``` +left = 0 +right = 0 +maxLen = 4 +``` + +--- + +### **Step 0: i=5, s\[5]=')'** + +``` +right = 1 +left = 0 +``` + +--- + +### **Step 1: i=4, s\[4]=')'** + +``` +right = 2 +left = 0 +``` + +--- + +### **Step 2: i=3, s\[3]='('** + +``` +right = 2 +left = 1 +``` + +--- + +### **Step 3: i=2, s\[2]=')'** + +``` +right = 3 +left = 1 +``` + +--- + +### **Step 4: i=1, s\[1]='('** + +``` +right = 3 +left = 2 +``` + +--- + +### **Step 5: i=0, s\[0]=')'** + +``` +right = 4 +left = 2 +``` + +--- + +* 右→左でも **maxLen = 4** で変化なし + +--- + +## **4. 結果** + +``` +maxLen = 4 +``` + +* 最長の有効部分文字列は `"()()"`(インデックス 1〜4) + +--- + +## **5. 計算量解析** + +* **時間計算量 O(n)** + + * 左右 2 回の走査のみ + +* **空間計算量 O(1)** + + * カウンタ変数 2 個のみ + +* **メモリ使用量の目安** + + * 追加メモリはほぼゼロ + +--- + +## **TypeScript 実装例(O(1) メモリ)** + +```typescript +function longestValidParenthesesO1(s: string): number { + let left = 0; + let right = 0; + let maxLen = 0; + + // 左→右 + for (const ch of s) { + if (ch === '(') left++; + else right++; + if (left === right) maxLen = Math.max(maxLen, 2 * right); + else if (right > left) left = right = 0; + } + + left = 0; + right = 0; + + // 右→左 + for (let i = s.length - 1; i >= 0; i--) { + const ch = s[i]; + if (ch === '(') left++; + else right++; + if (left === right) maxLen = Math.max(maxLen, 2 * left); + else if (left > right) left = right = 0; + } + + return maxLen; +} + +// 動作確認 +const testCases = [")()())", "(()", "()(())", "())((())"]; +for (const s of testCases) { + console.log(`Input: '${s}', Output: ${longestValidParenthesesO1(s)}`); +} +``` + +--- + +### **まとめ** + +* **O(n) スタック法**: 実装簡単・インデックス追跡可能 +* **O(1) 左右スキャン法**: メモリ効率最高・長さのみ取得可 + +--- + +次に、もし **左右スキャン法で最長部分文字列自体を復元する方法** も知りたい場合は、 +スタック法とのハイブリッドで可能です。 + +あなたは **長さだけ**で満足ですか、それとも**実際の部分文字列**も取得したいですか? diff --git a/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.js b/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.js new file mode 100644 index 00000000..b1afec34 --- /dev/null +++ b/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.js @@ -0,0 +1,79 @@ +// 以下は、指定された問題に対して **Node.js (18.16.1)** 向けに書かれた **関数ベースの JavaScript 解法** です。 +// この解法は **スタックを使った線形時間アルゴリズム(O(n))** で、処理時間・メモリ効率ともに良好です。 + +// --- + +// ### ✅ 解法:スタックベース + +// * 開き括弧 `'('` のインデックスをスタックに積む。 +// * 閉じ括弧 `')'` が出現したら、スタックのトップとペアになるかを判定。 +// * スタックに「最後に無効だったインデックス」を入れておき、ペア成立時に長さを更新。 + +// --- + +// ### ✅ 実装コード(コメント付き) + +// ```javascript +/** + * 最長の有効な括弧部分文字列の長さを返す + * + * @param {string} s - '(' と ')' のみからなる文字列 + * @returns {number} - 最長の well-formed な部分文字列の長さ + */ +function longestValidParenthesesJs(s) { + const stack = []; + // 初期値として -1 を入れておくことで、長さ計算時に基準になる + stack.push(-1); + + let maxLength = 0; + + for (let i = 0; i < s.length; i++) { + const c = s[i]; + + if (c === '(') { + // 開き括弧のインデックスをスタックに積む + stack.push(i); + } else { + // 閉じ括弧が出たので、対応する開き括弧をポップ + stack.pop(); + + if (stack.length === 0) { + // ペア不成立:現在の閉じ括弧は無効 → 新たな基準位置 + stack.push(i); + } else { + // ペア成立:現在位置と直前のインデックスとの差が有効長 + maxLength = Math.max(maxLength, i - stack[stack.length - 1]); + } + } + } + + return maxLength; +} +// ``` + +// --- + +// ### ✅ 使用例と出力確認 + +// ```javascript +// console.log(longestValidParentheses("(()")); // 2 +// console.log(longestValidParentheses(")()())")); // 4 +// console.log(longestValidParentheses("")); // 0 +// console.log(longestValidParentheses("()(()")); // 2 +// console.log(longestValidParentheses("()()")); // 4 +// ``` + +// --- + +// ### ✅ 時間・空間計算量 + +// | 指標 | 値 | +// | ------- | ------------------- | +// | 時間計算量 | O(n) (n は文字列長) | +// | 空間計算量 | O(n) (スタック用) | +// | 実行時間目安 | 約 60ms 以下(LeetCode) | +// | メモリ使用目安 | 約 40MB 以下(LeetCode) | + +// --- + +// ご希望があれば、**DP版の解法**や\*\*両方向スキャン版(O(1) space)\*\*もご紹介可能です。 diff --git a/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.py b/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.py new file mode 100644 index 00000000..a8bed56b --- /dev/null +++ b/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.py @@ -0,0 +1,84 @@ +# 以下に **Python (CPython 3.11.4)** での **LeetCode 向け class Solution 実装** を示します。 +# スタック法を用い、時間計算量 **O(n)**、空間計算量 **O(n)** で実装しています。 +# パラメータ・返却値は docstring で明示し、型ヒントを付けています。 + +# --- + +# ### 実装例(スタック法) + +# ```python +from typing import List + +class Solution: + def longestValidParentheses(self, s: str) -> int: + """ + LeetCode 用関数 + パラメータ: + s (str): '(' と ')' だけからなる文字列 (長さ 0 <= len(s) <= 3*10^4) + 戻り値: + int: 最長の有効な括弧部分文字列の長さ + + 計算量: + 時間計算量 O(n): 1 回の走査で判定可能 + 空間計算量 O(n): スタックに最大で n 要素保持 + """ + stack: List[int] = [-1] # スタックにインデックスを格納、初期値は基準位置 + max_len: int = 0 + + for i, char in enumerate(s): + if char == '(': + stack.append(i) # '(' の位置を push + else: + stack.pop() # ')' に対応する '(' を pop + if not stack: + # スタックが空なら現在位置を基準にする + stack.append(i) + else: + # 現在の有効長を更新 + max_len = max(max_len, i - stack[-1]) + + return max_len + + +# 動作確認用 +if __name__ == "__main__": + solver = Solution() + test_cases: List[str] = ["(()", ")()())", ""] + for s in test_cases: + print(f"Input: {s!r}, Output: {solver.longestValidParentheses(s)}") +# ``` + +# --- + +# ### 実行結果例 + +# ``` +# Input: '(()', Output: 2 +# Input: ')()())', Output: 4 +# Input: '', Output: 0 +# ``` + +# --- + +# ### 処理時間・メモリ消費量 + +# * **時間計算量**: O(n) + +# * 1回の走査で、スタック操作はすべて O(1) + +# * **空間計算量**: O(n) + +# * スタックが最大 n 要素になる可能性 + +# * **メモリ使用量の目安**: + +# * n=30,000 の場合 + +# * スタックの int 保持: 約 30,000 × 28byte ≒ 0.8MB 程度 + +# --- + +# もし **O(1) 追加メモリ** の左右スキャン法での最適化版も必要であれば、 +# LeetCode 用に最適化した実装も提示できます。 + +# あなたは次に、**O(1) メモリの最適版**も知りたいですか? diff --git a/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.ts b/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.ts new file mode 100644 index 00000000..f2cc0077 --- /dev/null +++ b/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/Longest-Valid-Parentheses.ts @@ -0,0 +1,83 @@ +// 以下に **TypeScript 5.1 (Node.js 18.16.1)** 向け、LeetCode 用の **O(n) スタック法** 実装を示します。 +// クラスは使わず、関数で処理し、パラメータ・返却値の型を明示しています。 +// スタックを用いるため、**時間計算量 O(n)**、**空間計算量 O(n)** です。 + +// --- + +// ### 実装例(TypeScript, O(n) スタック法) + +// ```typescript +/** + * 与えられた括弧文字列に対して、最長の有効な括弧部分文字列の長さを返します + * + * @param s - '(' と ')' のみからなる文字列 + * @returns number - 最長有効括弧部分文字列の長さ + * + * 時間計算量: O(n) - 文字列を1回走査し、スタック操作はO(1) + * 空間計算量: O(n) - スタックに最大n個のインデックスを保持 + */ +function longestValidParentheses(s: string): number { + const stack: number[] = [-1]; // インデックスを保持するスタック、初期値は基準位置 + let maxLen: number = 0; + + for (let i = 0; i < s.length; i++) { + if (s[i] === '(') { + stack.push(i); // '(' の位置を push + } else { + stack.pop(); // ')' に対応する '(' を pop + if (stack.length === 0) { + // スタックが空なら現在位置を基準にする + stack.push(i); + } else { + // 有効な長さを更新 + maxLen = Math.max(maxLen, i - stack[stack.length - 1]); + } + } + } + + return maxLen; +} + +// 動作確認 +const testCases: string[] = ["(()", ")()())", "", "()(())", "())((())"]; +for (const s of testCases) { + console.log(`Input: '${s}', Output: ${longestValidParentheses(s)}`); +} + +// ### 実行結果例 + +// ``` +// Input: '(()', Output: 2 +// Input: ')()())', Output: 4 +// Input: '', Output: 0 +// Input: '()(())', Output: 6 +// Input: '())((())', Output: 4 +// ``` + +// --- + +// ### 処理時間・メモリ消費量 + +// * **時間計算量**: O(n) + +// * スタック操作は O(1) なので全体で線形 + +// * **空間計算量**: O(n) + +// * インデックス用のスタックが最大で文字列長 n + +// * **メモリ使用量の目安**: + +// * n = 30,000 の場合 + +// * number\[] で 30,000 要素 ≒ 約 240KB 程度 (Node.js の number 8byte換算) + +// --- + +// もし **O(1) メモリの最適版**を TypeScript で書く場合も提示できますが、 +// まずこの **O(n) スタック法**を LeetCode に提出するのが一番安全です。 + +// --- + +// 質問です。 +// あなたは次に、**TypeScript で O(1) メモリの最適版**も知りたいですか? diff --git a/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/README.md b/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/README.md new file mode 100644 index 00000000..f7769380 --- /dev/null +++ b/DataStructures/Stacks/leetcode/32. Longest Valid Parentheses/README.md @@ -0,0 +1,204 @@ +承知しました。 +ここでは、**TypeScript (Node.js 18.16.1)** の **O(n) スタック法**を、**図解つきで具体的に解析**します。 + +例として以下の入力を解析します: + +``` +s = ")()())" +``` + +--- + +## 1. アルゴリズム概要 + +* **目的**: 最長の有効な括弧部分文字列の長さを求める +* **方法**: + + * スタックにインデックスを保持する + * `)` に対応する `(` を消すことで、有効な長さを算出 + * スタックが空になった場合は現在位置を基準にする + +--- + +### 初期状態 + +``` +stack = [-1] // 基準位置 +maxLen = 0 +``` + +* `-1` を最初に入れる理由 + → 最初の有効括弧長の計算時に基準点が必要 + +--- + +## 2. 処理の流れを図で解析 + +文字列: `")()())"` +インデックス: `0 1 2 3 4 5` + +--- + +### **Step 0: 初期** + +``` +stack = [-1] +maxLen = 0 +``` + +--- + +### **Step 1: i=0, s\[0]=')'** + +* `)` に対してスタックから pop +* スタックが空になったので現在のインデックスを push + +``` +pop -> stack = [] +stack empty -> push(0) +stack = [0] + +maxLen = 0 +``` + +**図解** + +``` +インデックス: 0 +文字列 : ) +スタック : [0] +``` + +--- + +### **Step 2: i=1, s\[1]='('** + +* `(` は push + +``` +push(1) +stack = [0, 1] +``` + +**図解** + +``` +インデックス: 0 1 +文字列 : ) ( +スタック : 0 1 +``` + +--- + +### **Step 3: i=2, s\[2]=')'** + +* `)` → pop → `1` を消す + +``` +pop -> stack = [0] +stack not empty +有効長 = i - stack[top] = 2 - 0 = 2 +maxLen = max(0, 2) = 2 +``` + +**図解** + +``` +インデックス: 0 1 2 +文字列 : ) ( ) +スタック : 0 +現在の有効長: 2 +``` + +--- + +### **Step 4: i=3, s\[3]='('** + +* `(` → push + +``` +stack = [0, 3] +``` + +**図解** + +``` +インデックス: 0 1 2 3 +文字列 : ) ( ) ( +スタック : 0 3 +``` + +--- + +### **Step 5: i=4, s\[4]=')'** + +* `)` → pop → `3` を消す + +``` +pop -> stack = [0] +stack not empty +有効長 = 4 - 0 = 4 +maxLen = max(2, 4) = 4 +``` + +**図解** + +``` +インデックス: 0 1 2 3 4 +文字列 : ) ( ) ( ) +スタック : 0 +現在の有効長: 4 +``` + +--- + +### **Step 6: i=5, s\[5]=')'** + +* `)` → pop → `0` を消す +* スタック空 → 現在のインデックスを push + +``` +pop -> stack = [] +stack empty -> push(5) +stack = [5] + +maxLen = 4 +``` + +**図解** + +``` +インデックス: 0 1 2 3 4 5 +文字列 : ) ( ) ( ) ) +スタック : 5 +``` + +--- + +## 3. 結果 + +* **maxLen = 4** +* 最長の有効部分文字列は `"()()"`(インデックス 1〜4) + +--- + +## 4. 計算量とメモリ解析 + +* **時間計算量 O(n)** + + * 各文字で push/pop 1 回ずつ → 線形時間 + +* **空間計算量 O(n)** + + * スタック最大長は n (全て '(' の場合) + * 例: n=30,000 → 30,000 × 8B ≒ 240KB + +--- + +この解析により、**スタックにインデックスを保持して差分を取るだけで有効長が求められる**ことがわかります。 + +--- + +もし次に、**O(1) メモリ版の左右スキャン法**も図解で解析してほしい場合は、続けて解説可能です。 + +あなたは次に、**O(1) メモリ版の図解解析**も見たいですか? diff --git a/DataStructures/Trees/BinaryIndexedTree/Other/binary numbers/README.md b/DataStructures/Trees/BinaryIndexedTree/Other/binary numbers/README.md new file mode 100644 index 00000000..5d00ffa3 --- /dev/null +++ b/DataStructures/Trees/BinaryIndexedTree/Other/binary numbers/README.md @@ -0,0 +1,241 @@ +以下では、先ほど提示した **Python (CPython 3.11.4)** のコードを題材に、 +各処理について **図解と詳細な解析** を行います。 + +--- + +## 1. 全体の処理の流れ + +``` +標準入力 + ↓ (sys.stdin.read) +文字列として一括取得 + ↓ (splitlines) +行ごとのリストに分割 + ↓ +1行目 = T (件数) +2行目以降 = 各X_i + ↓ (forループ) +各整数を2進数に変換 (bin(num)[2:]) + ↓ +結果をリストに追加 + ↓ (join) +まとめて出力 +``` + +--- + +## 2. コード解析と図解 + +### 2-1. 標準入力の読み込み + +```python +input_data: str = sys.stdin.read() +``` + +**解析**: + +* 標準入力全体を1つの文字列として読み込みます。 +* メリット: `input()` を何度も呼ぶより高速。 +* デメリット: 入力全体がメモリに載るが、T最大20万なので問題なし。 + +**図解**: + +``` +入力例 (標準入力) +----------------- +5 +6 +7 +10 +1 +15 + +sys.stdin.read() の結果 +----------------------- +"5\n6\n7\n10\n1\n15\n" +``` + +--- + +### 2-2. 行ごとの分割 + +```python +lines: List[str] = input_data.strip().splitlines() +``` + +**解析**: + +* `strip()` で末尾改行を削除 +* `splitlines()` で改行ごとに分割してリスト化 + +**図解**: + +``` +lines = ["5", "6", "7", "10", "1", "15"] +``` + +* `lines[0] = "5"` → 件数T +* `lines[1:] = ["6","7","10","1","15"]` → 処理対象 + +--- + +### 2-3. 件数の取得 + +```python +t: int = int(lines[0]) +``` + +**解析**: + +* 件数 `T=5` を整数化 + +**図解**: + +``` +t = 5 +``` + +--- + +### 2-4. 各整数を2進数に変換 + +```python +for i in range(1, t + 1): + num: int = int(lines[i]) + results.append(decimal_to_binary(num)) +``` + +**解析**: + +1. ループで順に整数を取得 +2. `decimal_to_binary` 関数で2進数に変換 +3. 結果リストに追加 + +--- + +#### decimal\_to\_binary 関数の処理 + +```python +def decimal_to_binary(num: int) -> str: + return bin(num)[2:] +``` + +* `bin(num)` は `0b` 付き文字列を返す +* `[2:]` で `0b` を削除 + +--- + +#### 変換のイメージ(例:`6`) + +``` +10進数 6 +↓ 2で割る +6 ÷ 2 = 3 ... 0 ← 2^0 +3 ÷ 2 = 1 ... 1 ← 2^1 +1 ÷ 2 = 0 ... 1 ← 2^2 +余りを下から読む → 110 + +bin(6) = "0b110" → [2:]で"110" +``` + +**図解: 2進数変換の木構造** + +``` + 6 + / \ + 余0 / \ 商3 + 3 + / \ + 余1/ \商1 + 1 + / \ + 余1/ \商0 (終了) + +2進数 = 110 (余りを逆順) +``` + +--- + +### 2-5. 出力の生成 + +```python +return "\n".join(results) +``` + +**解析**: + +* リストにためた結果を一括で改行区切り文字列に変換 +* `print` 1回で出力することで高速化&メモリ効率化 + +--- + +## 3. 処理時間とメモリの解析 + +* **入力規模**: + + * `T = 200,000` + * 各整数最大 `10^15` (16桁) +* **計算量**: + + * 変換1件あたり `O(log X_i)` ≈ 50程度(2進数桁数) + * 総計 `O(T log X_i)` → 実質高速 +* **メモリ使用量**: + + * 入力: \~数MB + * 結果リスト: \~数MB + * Pythonで十分処理可能 + +--- + +## 4. 入力例と出力例の流れ(図解) + +### 入力 + +``` +5 +6 +7 +10 +1 +15 +``` + +### 内部状態の変化 + +``` +lines = ["5","6","7","10","1","15"] +t = 5 + +ループ処理 +i=1 → num=6 → 110 +i=2 → num=7 → 111 +i=3 → num=10 → 1010 +i=4 → num=1 → 1 +i=5 → num=15 → 1111 + +results = ["110","111","1010","1","1111"] +``` + +### 出力 + +``` +110 +111 +1010 +1 +1111 +``` + +--- + +ここまでが **2進数変換問題の詳細解析** です。 + +--- + +次のステップとして、 +この解析を踏まえて **BIT (Binary Indexed Tree) クラス** の構築・更新・区間和処理の図解説明も可能です。 + +--- + +💡質問です: +次はこの続きを **BITの構築・更新処理を図解付きでPython実装** して解説しますか? diff --git a/DataStructures/Trees/BinaryIndexedTree/Other/binary numbers/binary-numbers.js b/DataStructures/Trees/BinaryIndexedTree/Other/binary numbers/binary-numbers.js new file mode 100644 index 00000000..4c378fa2 --- /dev/null +++ b/DataStructures/Trees/BinaryIndexedTree/Other/binary numbers/binary-numbers.js @@ -0,0 +1,93 @@ +// 以下に Node.js (v18.16.1) での実装例を示します。 +// 標準入力から fs を用いてデータを読み込み、大規模入力でも高速に処理できるようにしています。 +// 各処理は関数化し、パラメータや返却値はコメントにて明記しています。 + +// ```javascript +"use strict"; + +const fs = require("fs"); + +/** + * 10進数整数を2進数文字列に変換する関数 + * @param {bigint} num - 10進数の整数(最大10^15なのでBigInt対応) + * @returns {string} 2進数文字列(先頭0なし) + */ +function decimalToBinary(num) { + return num.toString(2); +} + +/** + * 標準入力から複数の整数を読み込み、2進数に変換して出力するメイン処理 + * @param {string} input - 標準入力の文字列 + * @returns {string} 出力文字列(改行区切り) + */ +function solve(input) { + const lines = input.trim().split("\n"); + const T = Number(lines[0]); + const results = new Array(T); + + for (let i = 0; i < T; i++) { + // 10^15までなのでBigIntで読み込み + const num = BigInt(lines[i + 1]); + results[i] = decimalToBinary(num); + } + + return results.join("\n"); +} + +// 標準入力から読み込み、処理結果を出力 +const input = fs.readFileSync(0, "utf8"); +console.log(solve(input)); + +// ### 実装ポイント + +// 1. **大規模入力対応** + +// * `fs.readFileSync(0, "utf8")` を用いて一括で標準入力を読み取り、 +// `split("\n")` で分割する方法を採用しています。 +// * `T` 最大 200,000 件でも十分高速に動作します。 + +// 2. **`BigInt` の使用** + +// * 入力は最大で $10^{15}$ なので、JavaScript の `Number` でも表現可能ですが、 +// 型安全のため `BigInt` で受け取り、`toString(2)` で2進数に変換します。 + +// 3. **メモリ消費抑制** + +// * 結果は `results` 配列に格納し、最後に `join("\n")` でまとめて出力。 +// 逐次 `console.log` するより高速でメモリ効率も良好です。 + +// --- + +// このコードは Node.js 18.16.1 環境で動作し、 +// 入力例を与えると以下のように出力されます。 + +// #### 入力例 + +// ``` +// 5 +// 6 +// 7 +// 10 +// 1 +// 15 +// ``` + +// #### 出力例 + +// ``` +// 110 +// 111 +// 1010 +// 1 +// 1111 +// ``` + +// --- + +// 次に、このコードを元に **BITの構築・更新・累積和計算** の処理を組み込んだ問題に発展させることが可能です。 + +// --- + +// 💡質問です: +// 次はこのBITクラスの **構築部分(問題3)** をJavaScriptで実装するところから進めますか? diff --git a/DataStructures/Trees/BinaryIndexedTree/Other/binary numbers/binary-numbers.py b/DataStructures/Trees/BinaryIndexedTree/Other/binary numbers/binary-numbers.py new file mode 100644 index 00000000..cab2aa5e --- /dev/null +++ b/DataStructures/Trees/BinaryIndexedTree/Other/binary numbers/binary-numbers.py @@ -0,0 +1,218 @@ +# 以下に Python (CPython 3.11.4) での解答例を示します。 +# 標準入力から整数を読み取り、2進数に変換して出力します。 +# 処理時間・メモリ消費を考慮して、**一括読み込み**と**リスト格納→まとめて出力**方式を採用しています。 + +# ```python +import sys +from typing import List + +def decimal_to_binary(num: int) -> str: + """ + 10進数整数を2進数文字列に変換する関数 + :param num: 10進数の整数 (最大10^15) + :return: 2進数表記の文字列(先頭0なし) + """ + return bin(num)[2:] + +def solve(input_data: str) -> str: + """ + 入力データを処理して各整数を2進数に変換した文字列を改行区切りで返す関数 + :param input_data: 標準入力の全データ + :return: 変換結果の文字列(改行区切り) + """ + lines: List[str] = input_data.strip().splitlines() + t: int = int(lines[0]) + results: List[str] = [] + + # メモリ消費を抑えるため、1行ずつ変換結果を格納 + for i in range(1, t + 1): + num: int = int(lines[i]) + results.append(decimal_to_binary(num)) + + return "\n".join(results) + + +if __name__ == "__main__": + # 標準入力を一括で読み込み + input_data: str = sys.stdin.read() + # 出力 + print(solve(input_data)) +# ``` + +# --- + +# ### 実装のポイント + +# 1. **処理時間** + +# * `sys.stdin.read()` で一括読み込みして分割するため、高速に処理可能です。 +# * `bin(num)[2:]` を使うことで内部C実装により変換が高速です。 + +# 2. **メモリ消費** + +# * 入力最大件数 `T = 200000` でも、リスト1つ分で十分処理可能です。 +# * 結果はまとめて `join` して一度だけ出力するため、`print` 多用による遅延を防ぎます。 + +# 3. **型アノテーション** + +# * `num: int` / `lines: List[str]` / `results: List[str]` と明示しています。 + +# --- + +# #### 実行例 + +# 入力: + +# ``` +# 5 +# 6 +# 7 +# 10 +# 1 +# 15 +# ``` + +# 出力: + +# ``` +# 110 +# 111 +# 1010 +# 1 +# 1111 +# ``` + +# --- + +# 次に進める場合、 +# このコードに **BIT (Binary Indexed Tree)** の構築処理を組み込みたいですか? +# それともまずは **BITクラスのPython実装** から進めますか? + +# 模範回答の改良版 + +# あなたが提示したコードは正しく全問正解できますが、**実行時間が長くなる原因**がいくつかあります。 +# 解析してみると、以下のような理由で Python では遅くなります。 + +# --- + +# ## 1. 標準入力の多重呼び出し + +# ```python +# T = int(input()) + +# for i in range(T): +# X = int(input()) +# ``` + +# * `input()` は **1行ずつ読み込むたびにバッファ処理とデコードが走る**。 +# * T = 200,000 の場合、20万回の `input()` 呼び出しはボトルネックになる。 + +# **改善策** + +# * `sys.stdin.read()` で一括読み込みして `split()` する方法にする。 +# * これで入力処理が数十倍高速化される。 + +# --- + +# ## 2. 文字列連結のたびに新しい文字列を作成 + +# ```python +# ans = str(X % 2) + ans +# ``` + +# * Pythonの文字列は **イミュータブル**(変更不可)。 +# * `+` で左に足すたびに **新しい文字列を毎回作成** する。 + +# ### 例:X = 15 の場合 + +# | ループ回数 | ansの長さ | 新しい文字列作成コスト | +# | ----- | ------ | ----------- | +# | 1回目 | 1 | O(1) | +# | 2回目 | 2 | O(2) | +# | 3回目 | 3 | O(3) | +# | 4回目 | 4 | O(4) | + +# * 桁数 n のとき O(n^2) に近いコストが発生 +# * 最大 10^15 ≈ 2^50 → 桁数 \~50 +# * これが 200,000 回だと文字列操作だけで数千万回分のコピーが発生 + +# **改善策** + +# * 右に追加して最後に `[::-1]` で反転する方が高速。 +# * さらに、`list.append()` して最後に `''.join(list[::-1])` するとより効率的。 + +# --- + +# ## 3. `print()` を毎回呼ぶ + +# ```python +# print(ans) +# ``` + +# * 標準出力は**非常に遅い**。 +# * 20万回呼び出すと I/O がボトルネックになる。 + +# **改善策** + +# * 文字列リストにためて最後に `'\n'.join(results)` でまとめて出力する。 + +# --- + +# ## 4. 総合的なボトルネック解析 + +# ``` +# 入力遅い (input 20万回) +# + +# 文字列連結遅い (左足し O(n^2)) +# + +# 出力遅い (print 20万回) +# = 実行時間が大きくなる +# ``` + +# --- + +# ## 高速化まとめ + +# | ボトルネック | 対策 | +# | ------------ | ------------------------------- | +# | `input()` 連呼 | `sys.stdin.read().split()` | +# | 左結合で文字列生成 | `append` + `reverse` or `bin()` | +# | `print()` 連呼 | まとめて `join` して1回出力 | + +# --- + +# ### 改善版コード(超高速) + +# ```python +# import sys + +# def solve(input_data: str) -> str: +# it = iter(input_data.split()) +# T = int(next(it)) +# results = [] +# for _ in range(T): +# x = int(next(it)) +# results.append(bin(x)[2:]) +# return "\n".join(results) + +# if __name__ == "__main__": +# print(solve(sys.stdin.read())) +# ``` + +# * **入力1回・出力1回・内部はC実装の`bin`使用** → 数十倍高速。 + +# --- + +# もしあなたのオリジナルコードを保ったまま改善するなら、 + +# 1. `sys.stdin.read()` で一括入力 +# 2. `ans = str(X%2)+ans` を **右結合 + reverse** に変更 +# 3. 出力をまとめて `join` + +# これだけでかなり高速化します。 + +# --- + +# 💡質問です: +# この「左結合によるO(n^2)」の挙動を図解するとさらに理解しやすいですが、 +# その図解も作ってみますか?