diff --git a/Algorithm/DynamicProgramming/other/Restore selected elements/README.md b/Algorithm/DynamicProgramming/other/Restore selected elements/README.md new file mode 100644 index 00000000..ea65f661 --- /dev/null +++ b/Algorithm/DynamicProgramming/other/Restore selected elements/README.md @@ -0,0 +1,200 @@ + +--- + +# 問題の概要: + +与えられた配列 `A = [a₁, a₂, ..., aₙ]` から、**いくつかの要素を選んで和を `K` にする**。 +ただし、**同じ位置の要素は1回まで使える(0-1制約)**。 +和が作れない場合は `-1` を出力し、 +作れる場合は**選んだ要素数が最小となる組み合わせ**を出力します。 + +--- + +# アルゴリズム:**動的計画法(0-1ナップサックDP)** + +## DPテーブル定義 + +* `dp[i][k]` + **「最初のi個までの要素から選んで、和をkにするときの最小個数」** + +* `prev[i][k]` + **「その状態に遷移する直前の和」**(復元用) + +--- + +# 処理イメージ:図解 + +## 例題 + +``` +A = [1, 3, 2, 2, 1] +K = 4 +``` + +## DPテーブル構築(状態遷移) + +### 初期化 + +``` +dp[0][0] = 0 (和が0のときは0個選ぶ) +dp[0][k≠0] = ∞ (初期は不可能) +``` + +--- + +### 配列イメージ(i=0) + +| k(和) | 0 | 1 | 2 | 3 | 4 | +| ---------- | - | - | - | - | - | +| dp\[0]\[k] | 0 | ∞ | ∞ | ∞ | ∞ | + +--- + +### i=1(a₁=1) + +* **使わない場合:** `dp[1][k] = dp[0][k]` +* **使う場合:** `dp[1][k+1] = min(dp[1][k+1], dp[0][k] + 1)` + +| k(和) | 0 | 1 | 2 | 3 | 4 | +| ---------- | - | - | - | - | - | +| dp\[1]\[k] | 0 | 1 | ∞ | ∞ | ∞ | + +--- + +### i=2(a₂=3) + +* **使わない場合:** dpをそのまま +* **使う場合:** + +``` +dp[2][3] = min(dp[2][3], dp[1][0]+1) => 1個(3だけ選択) +dp[2][4] = min(dp[2][4], dp[1][1]+1) => 2個(1+3) +``` + +| k(和) | 0 | 1 | 2 | 3 | 4 | +| ---------- | - | - | - | - | - | +| dp\[2]\[k] | 0 | 1 | ∞ | 1 | 2 | + +--- + +### i=3(a₃=2) + +``` +dp[3][2] = min(∞, dp[2][0]+1) => 1個(2) +dp[3][3] = min(1, dp[2][1]+1) => 1個(既にあるのでそのまま) +dp[3][4] = min(2, dp[2][2]+1) => 2個(2+2) +``` + +| k(和) | 0 | 1 | 2 | 3 | 4 | +| ---------- | - | - | - | - | - | +| dp\[3]\[k] | 0 | 1 | 1 | 1 | 2 | + +--- + +### i=4(a₄=2) + +* 同様に更新されるが、`dp[3][k]`が最適なので更新なし(同じ値が入る) + +--- + +### i=5(a₅=1) + +* 使わない場合:dp\[4]\[k]をコピー +* 使う場合: + +``` +dp[5][1] = min(1, dp[4][0]+1) => 1 +dp[5][2] = min(1, dp[4][1]+1) => 1 +dp[5][3] = min(1, dp[4][2]+1) => 1 +dp[5][4] = min(2, dp[4][3]+1) => 2 +``` + +--- + +# 最終DPテーブル + +| k(和) | 0 | 1 | 2 | 3 | 4 | +| ---------- | - | - | - | - | - | +| dp\[5]\[k] | 0 | 1 | 1 | 1 | 2 | + +--- + +# 復元処理(`prev`配列を使う) + +* `dp[5][4]=2` →「和4を作るため、`prev[5][4]=3`」 +* `dp[5][3]=1` →「和3を作るため、`prev[5][3]=1`」 + +### 復元した選択肢: + +``` +4 → 3 → 1 → 0 +``` + +選んだ数:`3,1` +(順番は逆でも良い) + +--- + +# 処理フロー全体 + +```plaintext +初期化 dp[0][0]=0 + +↓ +for i=1..N: + for k=0..K: + - 使わない場合 dp[i+1][k]=dp[i][k] + - 使う場合 dp[i+1][k+A[i]]=min(dp[i+1][k+A[i]], dp[i][k]+1) + +↓ +復元(prev配列から逆追跡) + +↓ +出力(個数 + 選んだ要素) +``` + +--- + +# 図まとめ(状態遷移) + +``` + i-1 + | + ┌──┴──┐ + 使わない 使う + ↓ ↓ + dp[i][k] dp[i][k+A[i]] = min(dp[i][k+A[i]], dp[i-1][k]+1) +``` + +--- + +# 計算量 + +| 項目 | 値 | +| ----- | -------- | +| 時間計算量 | O(N × K) | +| 空間計算量 | O(N × K) | + +--- + +# メモリ・時間計測 + +```javascript +const startTime = process.hrtime.bigint(); +...(処理)... +const endTime = process.hrtime.bigint(); +console.error(`Time: ${(endTime - startTime) / 1e6} ms`); + +const used = process.memoryUsage(); +console.error(`Memory: ${Math.round(used.heapUsed / 1024)} KB`); +``` + +--- + +# まとめ + +| 処理 | 内容 | +| ------ | --------------- | +| DP配列 | 和kを作る最小個数を保持 | +| prev配列 | 選んだ要素を復元するための履歴 | +| 復元処理 | 和Kから逆に辿る | diff --git a/Algorithm/DynamicProgramming/other/Restore selected elements/Restore-selected-elements.js b/Algorithm/DynamicProgramming/other/Restore selected elements/Restore-selected-elements.js new file mode 100644 index 00000000..8e15ea0e --- /dev/null +++ b/Algorithm/DynamicProgramming/other/Restore selected elements/Restore-selected-elements.js @@ -0,0 +1,133 @@ +// ご質問ありがとうございます。 +// 「**同じ位置の数字は複数回使えない場合**」は、\*\*部分和問題の典型的な制約付き版(0-1ナップサック問題)\*\*になります。 + +// --- + +// ## 問題の違い: + +// | 条件 | 使える数字 | +// | ------ | -------------------------- | +// | **前回** | 同じ数字は何度でも使える(無限個) | +// | **今回** | 同じ\*\*位置の数字は1回だけ使える(0-1制約) | + +// --- + +// ## 解法: + +// * 動的計画法(0-1ナップサック) +// * `dp[i][k]` :最初の i 個の数字から選んで和が k になるときの最小個数 +// * **復元用に`prev`配列を持つ** + +// --- + +// ## コード(Node.js) + +// ```javascript +const fs = require('fs'); + +/** + * 0-1制約付きの最小部分和問題 + * @param {number} N - 数列の長さ + * @param {number} K - 目標の和 + * @param {number[]} A - 数列A + * @returns {string} - 出力形式の文字列 + */ +function minSubsetSum_01(N, K, A) { + const INF = 1e9; + const dp = Array.from({ length: N + 1 }, () => new Array(K + 1).fill(INF)); + const prev = Array.from({ length: N + 1 }, () => new Array(K + 1).fill(-1)); // 復元用 + + dp[0][0] = 0; + + for (let i = 0; i < N; i++) { + for (let k = 0; k <= K; k++) { + // i番目の要素を使わない場合 + if (dp[i][k] < dp[i + 1][k]) { + dp[i + 1][k] = dp[i][k]; + prev[i + 1][k] = k; + } + // i番目の要素を使う場合 + if (k + A[i] <= K) { + if (dp[i][k] + 1 < dp[i + 1][k + A[i]]) { + dp[i + 1][k + A[i]] = dp[i][k] + 1; + prev[i + 1][k + A[i]] = k; // A[i]を使ってkからk+A[i]に遷移 + } + } + } + } + + if (dp[N][K] === INF) { + return '-1\n'; + } + + // 復元 + const res = []; + let k = K; + for (let i = N; i >= 1; i--) { + if (prev[i][k] !== k) { // i番目の要素を使った場合 + res.push(A[i - 1]); + k = prev[i][k]; + } + } + + return `${dp[N][K]}\n${res.join(' ')}\n`; +} + +// 入力 & 計測 +const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n'); +const [N, K] = input[0].split(' ').map(Number); +const A = input[1].split(' ').map(Number); + +const startTime = process.hrtime.bigint(); +const result = minSubsetSum_01(N, K, A); +const endTime = process.hrtime.bigint(); + +process.stdout.write(result); + +// パフォーマンス出力 +const used = process.memoryUsage(); +console.error(`Time: ${Number(endTime - startTime) / 1e6} ms`); +console.error(`Memory: ${Math.round(used.heapUsed / 1024)} KB`); +// ``` + +// --- + +// ## 入力例と出力 + +// ### 入力 + +// ``` +// 5 4 +// 1 3 2 2 1 +// ``` + +// ### 出力例 + +// ``` +// 2 +// 1 3 +// ``` + +// ※順序は任意(`3 1`でもOK) + +// --- + +// ## 制約まとめ: + +// * 各数字は**高々1回しか使えない**(同じ数字が複数あっても、同じ位置は1回だけ) +// * 解法は **0-1ナップサックDP** + +// --- + +// ## 計算量: + +// * 時間計算量:O(N × K) +// * 空間計算量:O(N × K) + +// --- + +// ## メモリと実行時間: + +// * `process.hrtime.bigint()`でナノ秒計測 +// * `process.memoryUsage()`でヒープ使用量出力 + diff --git a/Algorithm/DynamicProgramming/other/Restore selected elements/Restore-selected-elements.py b/Algorithm/DynamicProgramming/other/Restore selected elements/Restore-selected-elements.py new file mode 100644 index 00000000..68644e95 --- /dev/null +++ b/Algorithm/DynamicProgramming/other/Restore selected elements/Restore-selected-elements.py @@ -0,0 +1,185 @@ +# 以下は「**0-1部分和問題(最小個数)」の** +# **Python (CPython 3.11.4)** 解答例です。 + +# --- + +# ## 解法:**動的計画法(0-1ナップサック)** + +# ### 特徴: + +# * **同じ位置の数字は1回だけ使用可能(0-1制約)** +# * 最小個数で和`K`を作る +# * 解が存在しなければ`-1`を出力 +# * **時間とメモリ使用量も計測** + +# --- + +# ## コード + +import sys +import time +import tracemalloc +from typing import List, Tuple + +def min_subset_sum(N: int, K: int, A: List[int]) -> Tuple[int, List[int]]: + """ + 部分和最小個数問題(0-1ナップサック)の解法 + + Parameters: + - N : int + - K : int + - A : List[int] + + Returns: + - Tuple[int, List[int]] : (最小個数, 選んだ要素のリスト) + 解なしなら (-1, []) + """ + INF = 10**9 + dp: List[List[int]] = [[INF] * (K + 1) for _ in range(N + 1)] + prev: List[List[int]] = [[-1] * (K + 1) for _ in range(N + 1)] + + dp[0][0] = 0 + + for i in range(N): + for k in range(K + 1): + if dp[i][k] < dp[i + 1][k]: + dp[i + 1][k] = dp[i][k] + prev[i + 1][k] = k + if k + A[i] <= K: + if dp[i][k] + 1 < dp[i + 1][k + A[i]]: + dp[i + 1][k + A[i]] = dp[i][k] + 1 + prev[i + 1][k + A[i]] = k + + if dp[N][K] == INF: + return -1, [] + + # res の型を明示する + res: List[int] = [] + k = K + for i in range(N, 0, -1): + if prev[i][k] != k: + res.append(A[i - 1]) + k = prev[i][k] + + return dp[N][K], res + +def main() -> None: + """ + メイン実行関数。 + 標準入力からデータを受け取り、結果を標準出力に出力する。 + また、処理時間とメモリ使用量を標準エラーに出力する。 + """ + input_data = sys.stdin.read().strip().split('\n') + N, K = map(int, input_data[0].split()) + A = list(map(int, input_data[1].split())) + + # 計測開始 + start_time = time.perf_counter() + tracemalloc.start() + + cnt, res = min_subset_sum(N, K, A) + + # メモリ・時間計測終了 + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + end_time = time.perf_counter() + + # 結果出力 + if cnt == -1: + print(-1) + else: + print(cnt) + print(' '.join(map(str, res))) + + # パフォーマンス情報を標準エラー出力 + print(f"Time: {(end_time - start_time)*1000:.3f} ms", file=sys.stderr) + print(f"Memory: {peak // 1024} KB", file=sys.stderr) + +if __name__ == "__main__": + main() +# ``` + +# --- + +# ## 入出力例: + +# ### 入力: + +# ``` +# 5 4 +# 1 3 2 2 1 +# ``` + +# ### 出力: + +# ``` +# 2 +# 1 3 +# ``` + +# または + +# ``` +# 2 +# 3 1 +# ``` + +# (順序はどちらでもOK) + +# --- + +# ## 計測例(標準エラー出力) + +# ``` +# Time: 1.234 ms +# Memory: 1500 KB +# ``` + +# --- + +# ## 処理時間・メモリ計測: + +# | 項目 | 使用 | +# | ---- | --------------------- | +# | 処理時間 | `time.perf_counter()` | +# | メモリ | `tracemalloc` | + +# --- + +# ## 計算量: + +# | 項目 | 値 | +# | ----- | -------- | +# | 時間計算量 | O(N × K) | +# | 空間計算量 | O(N × K) | + +# --- + +# ## まとめ: + +# | 要素 | 内容 | +# | -------- | ---------------------------------- | +# | 使用言語 | Python (CPython 3.11.4) | +# | 型アノテーション | あり | +# | メモリ・時間計測 | `tracemalloc`, `time.perf_counter` | +# | 入力 | 標準入力 | +# | 出力 | 標準出力、パフォーマンス情報は`stderr` | + +# --- + +# ✅ これで解消する理由 +# res: List[int] と明示することで、 +# Pylanceが append の引数を「int型を追加している」と認識できるようになります。 + +# Tuple[int, List[int]] と返り値の型も明示したため、Pylanceの推論が完全に行われます。 + +# Pylance 型警告のよくある原因と対応 +# 原因 対応 +# append の型が不明 リストの型を明示する(List[int] など) +# 戻り値が推論できない 関数の戻り値に -> 型 を必ず指定 +# 配列の初期化時に型が消える List[List[int]] のように型付きで初期化 + +# 🔍 補足 +# Pylanceは高速ですが、型推論が途中で途切れると Unknown を返すことがあります。 +# --strict モードなどを使うとこういった警告が頻繁に出るため、必ず型注釈を入れましょう。 + diff --git a/DataStructures/Stacks/leetcode/20. Valid Parentheses/README.md b/DataStructures/Stacks/leetcode/20. Valid Parentheses/README.md new file mode 100644 index 00000000..58541aaa --- /dev/null +++ b/DataStructures/Stacks/leetcode/20. Valid Parentheses/README.md @@ -0,0 +1,188 @@ +**スタックを用いた括弧検証アルゴリズム**を図解し、各処理について具体的に説明します。 +--- + +# ① 基本的な動作フロー + +### **アルゴリズム概要** + +| 入力文字 | 動作 | スタック | +| ---- | ---------- | ------------- | +| `(` | Push | `[`(\`] | +| `[` | Push | `[`(\`\`\[\`] | +| `]` | Pop & チェック | `[`(\`] | +| `)` | Pop & チェック | (空) | + +--- + +## **図解:入力 s = "(\[])"** + +``` +初期状態: +stack = [] +``` + +### ステップ1: `(` を読む → **Push** + +``` +stack: [ + ( +] +``` + +--- + +### ステップ2: `[` を読む → **Push** + +``` +stack: [ + ( + [ +] +``` + +--- + +### ステップ3: `]` を読む → **Pop & チェック** + +``` +pop -> [ + +bracketMap[']'] = [ + +[ が取り出されたので OK + +stack: [ + ( +] +``` + +--- + +### ステップ4: `)` を読む → **Pop & チェック** + +``` +pop -> ( + +bracketMap[')'] = ( + +( が取り出されたので OK + +stack: [] +``` + +--- + +### **最終判定** + +* `stack` が空 → **true(有効な括弧列)** + +--- + +# ② 入力が不正な場合(例:"(\[)]") + +| 入力文字 | 動作 | スタック | +| ---- | ---------- | ------------------------------------- | +| `(` | Push | `[`(\`] | +| `[` | Push | `[`(\`\`\[\`] | +| `)` | Pop & チェック | `[`(`] → **失敗**(対応括弧が `\[`, 期待は `(\`) | + +--- + +## **図解:入力 s = "(\[)]"** + +``` +初期状態: +stack = [] +``` + +### ステップ1: `(` を読む → **Push** + +``` +stack: [ + ( +] +``` + +--- + +### ステップ2: `[` を読む → **Push** + +``` +stack: [ + ( + [ +] +``` + +--- + +### ステップ3: `)` を読む → **Pop & チェック** + +``` +pop -> [ + +bracketMap[')'] = ( + +[ が取り出されたが、期待は ( → **NG(不正)** + +即 return false +``` + +--- + +# ③ 流れまとめ(フローチャート) + +``` + +----------------+ + | 文字列を1文字ずつ読む | + +--------+-------+ + | + +----------+-----------+ + | 開き括弧か? | + | (, [, { | + +----------+-----------+ + |Yes + +--------v--------+ + | スタックにPush | + +-----------------+ + | + [次の文字] + | + |No(閉じ括弧) + +----------v----------+ + | スタックが空か? | + +----------+----------+ + |Yes + +--------v--------+ + | 不正(false) | + +----------------+ + | + |No + +----------v-----------+ + | スタックのTopと対応? | + +----------+-----------+ + |Yes |No + +--------v--------+ +----------------+ + | Popして次へ | | 不正(false) | + +-----------------+ +----------------+ +``` + +--- + +# ④ 時間・空間解析 + +| 項目 | 計算量 | +| -- | ------------------------ | +| 時間 | O(n)(1文字あたりPushまたはPopのみ) | +| 空間 | O(n)(全て開き括弧のとき最大) | + +--- + +# ⑤ まとめ + +* **有効な括弧列**:スタックで管理し、**対応する閉じ括弧でTopが一致するか確認** +* **不正な場合**: + + * スタックが空の状態で閉じ括弧が来る + * スタックTopと閉じ括弧の対応が間違っている +* **全処理後にスタックが空であれば正しい括弧列** diff --git a/DataStructures/Stacks/leetcode/20. Valid Parentheses/ValidParentheses.js b/DataStructures/Stacks/leetcode/20. Valid Parentheses/ValidParentheses.js new file mode 100644 index 00000000..530ca3d9 --- /dev/null +++ b/DataStructures/Stacks/leetcode/20. Valid Parentheses/ValidParentheses.js @@ -0,0 +1,88 @@ +// 承知しました。 +// 以下は、指定された括弧の有効性判定問題を\*\*JavaScript(Node.js 18.16.1 / LeetCode対応)\*\*でクラスを使わず関数形式で解いた実装です。 + +// --- + +// ## ✅ **問題再掲:** + +// **有効な括弧列**とは: + +// * `()[]{}` など、**開き括弧と閉じ括弧が正しく対応しているもの** +// * 開いた順に対応する閉じ括弧で閉じられているもの + +// --- + +// ## 🔧 **実装(JavaScript)** + +// ```javascript +/** + * 与えられた文字列sが有効な括弧列か判定する関数 + * + * @param {string} s - 入力文字列(括弧のみ: '()[]{}') + * @returns {boolean} - 有効な括弧列ならtrue、不正ならfalse + * + * 制約: 1 <= s.length <= 10^4 + * + * 時間計算量: O(n) (nは文字列の長さ) + * 空間計算量: O(n) (スタックを使用するため) + */ +function isValidJs(s) { + const stack = []; + + const bracketMap = { + ')': '(', + '}': '{', + ']': '[' + }; + + for (let char of s) { + if (char === '(' || char === '{' || char === '[') { + stack.push(char); + } else { + if (stack.length === 0) { + return false; + } + const top = stack.pop(); + if (top !== bracketMap[char]) { + return false; + } + } + } + + return stack.length === 0; +} +// ``` + +// --- + +// ## ✅ **実行例** + +// ```javascript +// console.log(isValid("()")); // true +// console.log(isValid("()[]{}")); // true +// console.log(isValid("(]")); // false +// console.log(isValid("([])")); // true +// console.log(isValid("([)]")); // false +// ``` + +// --- + +// ## 🔍 **処理時間・メモリ解析** + +// | 項目 | 計算量 | +// | --------- | ------------------- | +// | **時間計算量** | O(n)(各文字を一度だけ処理) | +// | **空間計算量** | O(n)(スタック使用時の最大サイズ) | + +// --- + +// ## ⚙️ **LeetCode対応ポイント** + +// * **Node.js 18.16.1対応** +// * クラスを使わず関数形式 +// * `console.log` によるテスト実行も可能 +// * `stack`を使うことで括弧の入れ子構造を適切に管理 + +// --- + +// もし、追加で**図解やアルゴリズム解説、ブラウザ動作版**が欲しければ教えてください! diff --git a/DataStructures/Stacks/leetcode/20. Valid Parentheses/ValidParentheses.py b/DataStructures/Stacks/leetcode/20. Valid Parentheses/ValidParentheses.py new file mode 100644 index 00000000..28b17ff1 --- /dev/null +++ b/DataStructures/Stacks/leetcode/20. Valid Parentheses/ValidParentheses.py @@ -0,0 +1,80 @@ +# 以下は、**LeetCode用のPython実装(CPython 3.11.4)**で、 +# 指定の括弧の妥当性判定を**クラス `Solution` 内メソッドで実装**したものです。 + +# ## ✅ 実装コード(Python) + +# ```python +from typing import List + +class Solution: + def isValid(self, s: str) -> bool: + """ + 与えられた文字列sが有効な括弧列か判定する関数 + + パラメーター: + s: str - 入力文字列(括弧のみ: '()[]{}') + + 返却値: + bool - 有効な括弧列ならTrue、不正ならFalse + + 制約: + 1 <= len(s) <= 10^4 + + 時間計算量: O(n) (nは文字列長) + 空間計算量: O(n) (スタック使用) + """ + stack: List[str] = [] + + bracket_map = { + ')': '(', + '}': '{', + ']': '[' + } + + for char in s: + if char in '({[': + stack.append(char) + else: + if not stack: + return False + top = stack.pop() + if bracket_map[char] != top: + return False + + return not stack +# ``` + +# --- + +# ## ✅ **使用例(テストコード)** + +# ```python +# # テスト実行例 +# solution = Solution() +# print(solution.isValid("()")) # True +# print(solution.isValid("()[]{}")) # True +# print(solution.isValid("(]")) # False +# print(solution.isValid("([])")) # True +# print(solution.isValid("([)]")) # False +# ``` + +# --- + +# ## 🔍 **解析** + +# | 項目 | 計算量 | +# | --------- | ---------------------------- | +# | **時間計算量** | O(n) (各文字を一度だけ処理) | +# | **空間計算量** | O(n) (最悪ケースでスタックに全ての開き括弧が入る) | + +# --- + +# ## 🔧 **特徴** + +# * **LeetCode用構造** → `class Solution` に定義 +# * **型ヒント付き**(`List[str]`、`-> bool`) +# * **最適化済み** → 余計な処理なし、シンプルかつ効率的 + +# --- + +# もし、追加で**スタック動作の図解、再帰解法や例外ケースの説明**が必要であればお伝えください! diff --git a/DataStructures/Stacks/leetcode/20. Valid Parentheses/ValidParentheses.ts b/DataStructures/Stacks/leetcode/20. Valid Parentheses/ValidParentheses.ts new file mode 100644 index 00000000..28ec4cd5 --- /dev/null +++ b/DataStructures/Stacks/leetcode/20. Valid Parentheses/ValidParentheses.ts @@ -0,0 +1,74 @@ +// 以下は、TypeScriptで括弧の妥当性を判定する関数実装です。 +// LeetCode環境(TypeScript 5.1, Node.js 18.16.1)に対応し、**クラスは使わず関数形式**で記述しています。 + +// ### 処理時間とメモリ消費: + +// * **時間計算量**: O(n)(nは文字列長) +// * **空間計算量**: O(n)(スタック使用) + +// --- + +// ```typescript +/** + * 与えられた文字列が括弧として正しいかを判定する関数。 + * + * @param s - 入力文字列('(', ')', '{', '}', '[', ']' からなる) + * @returns boolean - 正しい括弧列ならtrue、不正ならfalse + * + * 制約: 1 <= s.length <= 10^4 + * 時間計算量: O(n) + * 空間計算量: O(n)(スタックを使用するため) + */ +function isValid(s: string): boolean { + const stack: string[] = []; + + // 括弧の対応表 + const bracketMap: Record = { + ')': '(', + '}': '{', + ']': '[', + }; + + for (const char of s) { + // 開き括弧ならスタックにpush + if (char === '(' || char === '{' || char === '[') { + stack.push(char); + } else { + // 閉じ括弧の処理 + if (stack.length === 0) { + return false; // 対応する開き括弧なし + } + const top = stack.pop(); + if (top !== bracketMap[char]) { + return false; // 対応が間違っている + } + } + } + + // 全ての括弧が閉じられているか + return stack.length === 0; +} +// ``` + +// --- + +// ## 実行例: + +// ```typescript +// console.log(isValid("()")); // true +// console.log(isValid("()[]{}")); // true +// console.log(isValid("(]")); // false +// console.log(isValid("([])")); // true +// console.log(isValid("([)]")); // false +// ``` + +// --- + +// ## メモリ使用についての補足: + +// * **最悪ケース**(全て開き括弧の場合)でスタックに最大 `n` 個要素を保持するため、O(n) メモリを消費します。 +// * LeetCode環境では 10^4程度の文字列長でも十分実行可能です。 + +// --- + +// もし図解や、実行時スタックの動作説明が必要であれば教えてください! diff --git a/Mathematics/Fibonacci number/atcoder/B28/B28.go b/Mathematics/Fibonacci number/atcoder/B28/B28.go new file mode 100644 index 00000000..0b640d26 --- /dev/null +++ b/Mathematics/Fibonacci number/atcoder/B28/B28.go @@ -0,0 +1,130 @@ +// 以下に、**Go 1.20.6** によるフィボナッチ数列の第 N 項を $10^9+7$ で割った余りを高速に求める実装を提示します。 + +// --- + +// ## ✅ 実装方針 + +// | 項目 | 内容 | +// | ----- | ----------------------- | +// | 時間計算量 | `O(N)`(1ループ) | +// | 空間計算量 | `O(1)`(配列不使用) | +// | 型の明示 | すべての関数と変数に明確な型を付与 | +// | メモリ効率 | スタック上の変数3つのみ使用 | +// | 入出力 | `fmt.Scan` を使用(高速 & 標準) | + +// --- + +// ## 🧮 Go (1.20.6) 実装コード + +package main + +import ( + "fmt" +) + +const MOD int = 1_000_000_007 + +/** + * fibonacciMod は、フィボナッチ数列の第 n 項を MOD(10^9+7) で割った余りを返します。 + * + * @param n int フィボナッチ数列の項番号(3 <= n <= 10^7) + * @return int 第 n 項を 10^9 + 7 で割った余り + */ +func fibonacciMod(n int) int { + if n == 1 || n == 2 { + return 1 + } + + var a, b int = 1, 1 // a: F(n-2), b: F(n-1) + for i := 3; i <= n; i++ { + next := (a + b) % MOD + a = b + b = next + } + return b +} + +func main() { + var n int + _, err := fmt.Scan(&n) + if err != nil { + panic("入力エラー") + } + + result := fibonacciMod(n) + fmt.Println(result) +} + +// ## ✅ 処理ステップの解説(図解) + +// ``` +// 初期状態: +// a = 1 (F₁) +// b = 1 (F₂) + +// i = 3: +// next = (a + b) % MOD = 2 +// a = 1, b = 2 + +// i = 4: +// next = (1 + 2) % MOD = 3 +// a = 2, b = 3 + +// ... + +// i = N: +// 結果は b(Fₙ) +// ``` + +// --- + +// ## 📈 性能と制約対応 + +// | 性能項目 | 内容 | +// | ------ | ------------------------- | +// | 時間計算量 | O(N) | +// | 空間計算量 | O(1)(変数3つのみ) | +// | 実行時間 | N = 10^7 でも 0.2 ~ 0.5 秒程度 | +// | メモリ使用量 | 数十KB程度 | +// | 対応制約 | `3 ≤ N ≤ 10^7` 完全対応 | + +// --- + +// ## 🧪 テスト例 + +// ### 入力 + +// ``` +// 6 +// ``` + +// ### 出力 + +// ``` +// 8 +// ``` + +// --- + +// ## ✅ 実行方法 + +// ```bash +// go run main.go +// ``` + +// または、標準入力からの入力付きで: + +// ```bash +// echo 8691200 | go run main.go +// ``` + +// --- + +// ## ✅ 補足:高速化を希望する場合 + +// * `O(log N)` の **行列累乗法** や **Doubling法** による高速フィボナッチ計算も可能です。 +// * ご希望があれば図付きでご案内します。 + +// --- + +// 以上が Go (1.20.6) による、シンプルかつ高性能なフィボナッチ計算実装です。必要があれば高速バージョンの提供も可能です。 diff --git a/Mathematics/Fibonacci number/atcoder/B28/B28.js b/Mathematics/Fibonacci number/atcoder/B28/B28.js new file mode 100644 index 00000000..275df77b --- /dev/null +++ b/Mathematics/Fibonacci number/atcoder/B28/B28.js @@ -0,0 +1,86 @@ +// 以下は、指定された条件(Node.js / `fs` を使った標準入力 / 実行時間とメモリ制限内)に従い、**フィボナッチ数列の第 N 項を $10^9+7$ で割った余り**を計算するJavaScriptコードです。 + +// --- + +// ### ✅ 実装のポイント + +// * 定数 `MOD = 1_000_000_007` を使って **オーバーフローを防止**。 +// * **繰り返し計算(O(N))** を使用(メモリを抑えつつ高速)。 +// * `BigInt` は使いません(今回は必要ありません)。 +// * **配列を使わず2変数だけ保持**して、**メモリ効率を最大限に向上**。 + +// --- + +// ### 📦 JavaScript (Node.js 18.16.1) 実装 + +// ```javascript +// 入力処理(標準入力) +const fs = require('fs'); +const input = fs.readFileSync('/dev/stdin', 'utf8').trim(); +const N = parseInt(input); + +// フィボナッチ数列の第 N 項を MOD で割った余りを返す +// @param {number} n - 求めたい項の番号 +// @return {number} - フィボナッチ数列の第 n 項の MOD(10^9+7)余り +function fibonacciMod(n) { + const MOD = 1_000_000_007; + if (n === 1 || n === 2) return 1; + + let a = 1; // a(n-2) + let b = 1; // a(n-1) + for (let i = 3; i <= n; i++) { + const next = (a + b) % MOD; + a = b; + b = next; + } + return b; +} + +// 出力 +console.log(fibonacciMod(N)); +// ``` + +// --- + +// ### 📈 実行性能 + +// * **時間計算量**: `O(N)` (ループ1回) +// * **空間計算量**: `O(1)` (変数2つのみ使用) +// * **実行時間目安**: + +// * `N = 10^7` でも **数百ms〜1秒未満**(Node.jsのループ処理高速) +// * **メモリ使用量**: + +// * 数十KB程度(**配列未使用**) + +// --- + +// ### 🧪 テスト例 + +// #### 入力: + +// ``` +// 6 +// ``` + +// #### 出力: + +// ``` +// 8 +// ``` + +// #### 入力: + +// ``` +// 8691200 +// ``` + +// #### 出力: + +// ``` +// 922041576 +// ``` + +// --- + +// さらに高速化を求める場合、`O(log N)` の**行列累乗**法に切り替えることも可能です。希望があればその版もご提供できます。 diff --git a/Mathematics/Fibonacci number/atcoder/B28/B28.php b/Mathematics/Fibonacci number/atcoder/B28/B28.php new file mode 100644 index 00000000..6b7d73d0 --- /dev/null +++ b/Mathematics/Fibonacci number/atcoder/B28/B28.php @@ -0,0 +1,126 @@ +