From 0404b0cd4e34ad0dec113e699546a5b2df852a34 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 1 Feb 2026 13:49:27 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E7=B5=90=E6=9E=9C=E3=81=AB=E5=9F=BA=E3=81=A5=E3=81=8F=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 修正概要 3つのLeetCode問題(2621 Sleep、2622 Cache With Time Limit、2623 Memoize)に対するレビュー指摘事項を修正 ## 2622. Cache With Time Limit - ipynb: メタデータをtypescriptに変更、cleanup()を1パス削除に最適化 - README: メモリリーク記述の正確化、ベンチマーク値に参考値明記 - README_react.html: React本番ビルド使用、自動再生間隔を定数化 ## 2623. Memoize - ipynb: メタデータ修正、キー衝突対策、二重検索回避 - README: 文法・用語修正、可変長関数の注意書き追加 ## 2621. Sleep - ipynb: メタデータ修正、バリデーション順序改善 - README: 非推奨API修正、マイクロ最適化主張の見直し --- .../Claude Code Sonnet 4.5/README.md | 6 +- .../Claude Code Sonnet 4.5/Sleep_TS.ipynb | 308 +++++++------- .../CacheWithTimeLimit_TS.ipynb | 17 +- .../Claude Code Sonnet 4.5/README.md | 26 +- .../Claude Code Sonnet 4.5/README_react.html | 17 +- .../Claude Code Sonnet 4.5/Memoize_TS.ipynb | 380 +++++++++--------- .../Claude Code Sonnet 4.5/README.md | 13 +- 7 files changed, 383 insertions(+), 384 deletions(-) diff --git a/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/README.md b/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/README.md index 5970f1ca..30d12930 100644 --- a/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/README.md +++ b/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/README.md @@ -189,7 +189,7 @@ async def sleep(millis: int) -> None: raise ValueError("millis must be an integer between 1 and 1000") # イベントループ取得 - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() # 実行中のイベントループ取得(Python 3.10+推奨) # Future 作成(コルーチンが待機するオブジェクト) future: asyncio.Future[None] = loop.create_future() @@ -256,7 +256,7 @@ async def sleep(millis: int) -> None: MILLIS_TO_SECONDS = 0.001 async def sleep(millis: int) -> None: - await asyncio.sleep(millis * MILLIS_TO_SECONDS) # 除算より乗算が高速 + await asyncio.sleep(millis * MILLIS_TO_SECONDS) # 可読性とメンテナンス性の向上 ``` ### パフォーマンスノート @@ -354,7 +354,7 @@ async function sleep(millis: number): Promise { // Python 等価実装 async def sleep(millis: int) -> None: - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() # 実行中のイベントループ取得 future = loop.create_future() loop.call_later(millis / 1000, future.set_result, None) await future diff --git a/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/Sleep_TS.ipynb b/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/Sleep_TS.ipynb index d528405d..55a710ec 100644 --- a/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/Sleep_TS.ipynb +++ b/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/Sleep_TS.ipynb @@ -1,155 +1,155 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "2aa72022", - "metadata": {}, - "source": [ - "# TypeScript Sleep関数実装\n", - "\n", - "## 1. 問題の分析\n", - "\n", - "### 競技プログラミング視点での分析\n", - "- **実行速度**: 非同期処理の仕組み上、実行速度は`setTimeout`のブラウザ/Node.jsエンジンの実装に依存\n", - "- **メモリ使用量**: Promise1つとタイマーIDのみで、O(1)の極小メモリ\n", - "- **最適化ポイント**: シンプルな実装が最速(余計な処理を追加しない)\n", - "\n", - "### 業務開発視点での分析\n", - "- **型安全性**: `millis`が正の整数であることの保証、Promise型の明示\n", - "- **エラーハンドリング**: 不正な入力値(負の数、0、非数値)への対応\n", - "- **保守性**: 明確な関数シグネチャとドキュメント\n", - "- **可読性**: 非同期処理の意図が明確\n", - "\n", - "### TypeScript特有の考慮点\n", - "- **型推論**: `Promise`の明示的な型定義\n", - "- **strict mode**: nullチェック、型安全性の確保\n", - "- **async/await**: Promiseラッパーの簡潔な記述\n", - "- **型ガード**: 実行時の入力検証\n", - "\n", - "## 2. アルゴリズムアプローチ比較\n", - "\n", - "| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |\n", - "|---------|---------|--------|----------|--------|-------|------|\n", - "| setTimeout + Promise | O(1) | O(1) | 低 | 高 | 高 | 標準的で最適 |\n", - "| setInterval + clearInterval | O(1) | O(1) | 中 | 中 | 低 | 不要な複雑性 |\n", - "| busy wait (while loop) | O(n) | O(1) | 低 | 高 | 低 | CPU使用率100%で非推奨 |\n", - "| Promise.race + setTimeout | O(1) | O(1) | 中 | 中 | 中 | 過剰設計 |\n", - "\n", - "## 3. 選択したアルゴリズムと理由\n", - "\n", - "### 選択したアプローチ\n", - "**setTimeout + Promise wrapper**\n", - "\n", - "### 理由\n", - "- **計算量的な優位性**: O(1)の時間・空間計算量で最適\n", - "- **TypeScript環境での型安全性**: Promise型の明確な定義が可能\n", - "- **保守性・可読性**: 非同期処理の標準パターンで理解しやすい\n", - "- **実装の単純性**: コード行数が最小で、バグの混入リスクが低い\n", - "\n", - "### TypeScript特有の最適化ポイント\n", - "- **型推論の活用**: `Promise`で戻り値の型を明示\n", - "- **readonly修飾子**: 入力値の不変性を保証(必要に応じて)\n", - "- **strict nullチェック**: 実行時エラーの防止\n", - "- **エラー型の明示**: TypeErrorによる型レベルでのエラー情報\n", - "\n", - "## 4. 実装コード\n", - "\n", - "```typescript\n", - "// Analyze Complexity\n", - "// Runtime 45 ms\n", - "// Beats 55.94%\n", - "// Memory 53.75 MB\n", - "// Beats 88.64%\n", - "/**\n", - " * 指定されたミリ秒数だけ非同期に待機する関数\n", - " * \n", - " * @param millis - 待機するミリ秒数(正の整数)\n", - " * @returns void を解決するPromise\n", - " * @throws {TypeError} millis が数値でない場合\n", - " * @throws {RangeError} millis が範囲外(1-1000)の場合\n", - " * @complexity Time: O(1), Space: O(1)\n", - " * \n", - " * @example\n", - " * const start = Date.now();\n", - " * await sleep(100);\n", - " * console.log(Date.now() - start); // ~100\n", - " */\n", - "async function sleep(millis: number): Promise {\n", - " // 型ガード: 数値チェック\n", - " if (typeof millis !== 'number' || Number.isNaN(millis)) {\n", - " throw new TypeError('millis must be a valid number');\n", - " }\n", - " \n", - " // 範囲チェック(制約条件: 1 <= millis <= 1000)\n", - " if (millis < 1 || millis > 1000) {\n", - " throw new RangeError('millis must be between 1 and 1000');\n", - " }\n", - " \n", - " // 整数チェック(正の整数要件)\n", - " if (!Number.isInteger(millis)) {\n", - " throw new RangeError('millis must be an integer');\n", - " }\n", - " \n", - " // Promise でラップした setTimeout による非同期待機\n", - " return new Promise((resolve) => {\n", - " setTimeout(resolve, millis);\n", - " });\n", - "}\n", - "```\n", - "\n", - "### LeetCode提出用の最小実装\n", - "\n", - "問題の制約条件が保証されている場合、エラーハンドリングを省略した最小実装:\n", - "\n", - "```typescript\n", - "// Analyze Complexity\n", - "// Runtime 47 ms\n", - "// Beats 42.89%\n", - "// Memory 55.27 MB\n", - "// Beats 37.58%\n", - "async function sleep(millis: number): Promise {\n", - " return new Promise((resolve) => {\n", - " setTimeout(resolve, millis);\n", - " });\n", - "}\n", - "```\n", - "\n", - "## TypeScript固有の最適化観点\n", - "\n", - "### 型安全性の活用\n", - "\n", - "1. **コンパイル時エラー防止**\n", - " - `Promise`の明示的な型定義により、戻り値の誤用を防止\n", - " - `millis: number`により、文字列などの不正な型の渡し込みを防止\n", - "\n", - "2. **実行時型チェック**\n", - " - `typeof`および`Number.isNaN`による実行時検証\n", - " - `Number.isInteger`による整数チェック\n", - "\n", - "3. **エラー型の明示**\n", - " - `TypeError`: 型の不一致\n", - " - `RangeError`: 値の範囲外\n", - "\n", - "### パフォーマンス特性\n", - "\n", - "- **時間計算量**: O(1) - 定数時間での処理完了\n", - "- **空間計算量**: O(1) - Promise1つとタイマーIDのみ\n", - "- **実行時オーバーヘッド**: 最小限(Promiseラッパーのみ)\n", - "\n", - "### 開発効率と保守性\n", - "\n", - "- **IntelliSense**: 型定義により引数と戻り値が自動補完\n", - "- **リファクタリング安全性**: 型チェックにより変更時のエラーを検出\n", - "- **ドキュメント**: JSDocコメントによる使用方法の明示\n", - "- **テスタビリティ**: async/awaitにより同期的なテストコードが記述可能" - ] - } - ], - "metadata": { - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} + "cells": [ + { + "cell_type": "markdown", + "id": "2aa72022", + "metadata": {}, + "source": [ + "# TypeScript Sleep関数実装\n", + "\n", + "## 1. 問題の分析\n", + "\n", + "### 競技プログラミング視点での分析\n", + "- **実行速度**: 非同期処理の仕組み上、実行速度は`setTimeout`のブラウザ/Node.jsエンジンの実装に依存\n", + "- **メモリ使用量**: Promise1つとタイマーIDのみで、O(1)の極小メモリ\n", + "- **最適化ポイント**: シンプルな実装が最速(余計な処理を追加しない)\n", + "\n", + "### 業務開発視点での分析\n", + "- **型安全性**: `millis`が正の整数であることの保証、Promise型の明示\n", + "- **エラーハンドリング**: 不正な入力値(負の数、0、非数値)への対応\n", + "- **保守性**: 明確な関数シグネチャとドキュメント\n", + "- **可読性**: 非同期処理の意図が明確\n", + "\n", + "### TypeScript特有の考慮点\n", + "- **型推論**: `Promise`の明示的な型定義\n", + "- **strict mode**: nullチェック、型安全性の確保\n", + "- **async/await**: Promiseラッパーの簡潔な記述\n", + "- **型ガード**: 実行時の入力検証\n", + "\n", + "## 2. アルゴリズムアプローチ比較\n", + "\n", + "| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |\n", + "|---------|---------|--------|----------|--------|-------|------|\n", + "| setTimeout + Promise | O(1) | O(1) | 低 | 高 | 高 | 標準的で最適 |\n", + "| setInterval + clearInterval | O(1) | O(1) | 中 | 中 | 低 | 不要な複雑性 |\n", + "| busy wait (while loop) | O(n) | O(1) | 低 | 高 | 低 | CPU使用率100%で非推奨 |\n", + "| Promise.race + setTimeout | O(1) | O(1) | 中 | 中 | 中 | 過剰設計 |\n", + "\n", + "## 3. 選択したアルゴリズムと理由\n", + "\n", + "### 選択したアプローチ\n", + "**setTimeout + Promise wrapper**\n", + "\n", + "### 理由\n", + "- **計算量的な優位性**: O(1)の時間・空間計算量で最適\n", + "- **TypeScript環境での型安全性**: Promise型の明確な定義が可能\n", + "- **保守性・可読性**: 非同期処理の標準パターンで理解しやすい\n", + "- **実装の単純性**: コード行数が最小で、バグの混入リスクが低い\n", + "\n", + "### TypeScript特有の最適化ポイント\n", + "- **型推論の活用**: `Promise`で戻り値の型を明示\n", + "- **readonly修飾子**: 入力値の不変性を保証(必要に応じて)\n", + "- **strict nullチェック**: 実行時エラーの防止\n", + "- **エラー型の明示**: TypeErrorによる型レベルでのエラー情報\n", + "\n", + "## 4. 実装コード\n", + "\n", + "```typescript\n", + "// Analyze Complexity\n", + "// Runtime 45 ms\n", + "// Beats 55.94%\n", + "// Memory 53.75 MB\n", + "// Beats 88.64%\n", + "/**\n", + " * 指定されたミリ秒数だけ非同期に待機する関数\n", + " * \n", + " * @param millis - 待機するミリ秒数(正の整数)\n", + " * @returns void を解決するPromise\n", + " * @throws {TypeError} millis が数値でない場合\n", + " * @throws {RangeError} millis が範囲外(1-1000)の場合\n", + " * @complexity Time: O(1), Space: O(1)\n", + " * \n", + " * @example\n", + " * const start = Date.now();\n", + " * await sleep(100);\n", + " * console.log(Date.now() - start); // ~100\n", + " */\n", + "async function sleep(millis: number): Promise {\n", + " // 型ガード: 数値チェック\n", + " if (typeof millis !== 'number' || Number.isNaN(millis)) {\n", + " throw new TypeError('millis must be a valid number');\n", + " }\n", + " \n", + " // 整数チェック(正の整数要件)\n", + " if (!Number.isInteger(millis)) {\n", + " throw new RangeError('millis must be an integer');\n", + " }\n", + " \n", + " // 範囲チェック(制約条件: 1 <= millis <= 1000)\n", + " if (millis < 1 || millis > 1000) {\n", + " throw new RangeError('millis must be between 1 and 1000');\n", + " }\n", + " \n", + " // Promise でラップした setTimeout による非同期待機\n", + " return new Promise((resolve) => {\n", + " setTimeout(resolve, millis);\n", + " });\n", + "}\n", + "```\n", + "\n", + "### LeetCode提出用の最小実装\n", + "\n", + "問題の制約条件が保証されている場合、エラーハンドリングを省略した最小実装:\n", + "\n", + "```typescript\n", + "// Analyze Complexity\n", + "// Runtime 47 ms\n", + "// Beats 42.89%\n", + "// Memory 55.27 MB\n", + "// Beats 37.58%\n", + "async function sleep(millis: number): Promise {\n", + " return new Promise((resolve) => {\n", + " setTimeout(resolve, millis);\n", + " });\n", + "}\n", + "```\n", + "\n", + "## TypeScript固有の最適化観点\n", + "\n", + "### 型安全性の活用\n", + "\n", + "1. **コンパイル時エラー防止**\n", + " - `Promise`の明示的な型定義により、戻り値の誤用を防止\n", + " - `millis: number`により、文字列などの不正な型の渡し込みを防止\n", + "\n", + "2. **実行時型チェック**\n", + " - `typeof`および`Number.isNaN`による実行時検証\n", + " - `Number.isInteger`による整数チェック\n", + "\n", + "3. **エラー型の明示**\n", + " - `TypeError`: 型の不一致\n", + " - `RangeError`: 値の範囲外\n", + "\n", + "### パフォーマンス特性\n", + "\n", + "- **時間計算量**: O(1) - 定数時間での処理完了\n", + "- **空間計算量**: O(1) - Promise1つとタイマーIDのみ\n", + "- **実行時オーバーヘッド**: 最小限(Promiseラッパーのみ)\n", + "\n", + "### 開発効率と保守性\n", + "\n", + "- **IntelliSense**: 型定義により引数と戻り値が自動補完\n", + "- **リファクタリング安全性**: 型チェックにより変更時のエラーを検出\n", + "- **ドキュメント**: JSDocコメントによる使用方法の明示\n", + "- **テスタビリティ**: async/awaitにより同期的なテストコードが記述可能" + ] + } + ], + "metadata": { + "language_info": { + "name": "typescript" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/CacheWithTimeLimit_TS.ipynb b/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/CacheWithTimeLimit_TS.ipynb index 065fc2e5..49504fa3 100644 --- a/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/CacheWithTimeLimit_TS.ipynb +++ b/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/CacheWithTimeLimit_TS.ipynb @@ -290,7 +290,7 @@ " /**\n", " * 未期限切れキーの数を取得\n", " * @returns アクティブなキーの数\n", - " * @complexity Time: O(n), Space: O(1)\n", + " * @complexity Time: O(n), Space: O(n)\n", " */\n", " count(): number {\n", " const now = Date.now();\n", @@ -330,21 +330,16 @@ " \n", " /**\n", " * 期限切れエントリを削除(内部ヘルパー)\n", - " * @complexity Time: O(n), Space: O(1)\n", + " * @complexity Time: O(n), Space: O(n)\n", " */\n", " private cleanup(): void {\n", " const now = Date.now();\n", - " const keysToDelete: number[] = [];\n", " \n", - " for (const [key, entry] of this.cache.entries()) {\n", + " for (const [key, entry] of this.cache) {\n", " if (entry.expiresAt <= now) {\n", - " keysToDelete.push(key);\n", + " this.cache.delete(key);\n", " }\n", " }\n", - " \n", - " for (const key of keysToDelete) {\n", - " this.cache.delete(key);\n", - " }\n", " }\n", " \n", " set(key: number, value: number, duration: number): boolean {\n", @@ -404,9 +399,9 @@ ], "metadata": { "language_info": { - "name": "python" + "name": "typescript" } }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README.md b/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README.md index 7db18910..a3fad594 100644 --- a/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README.md +++ b/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README.md @@ -137,7 +137,8 @@ flowchart TD **終了性**: - `count` の走査は有限回(最大100エントリ) -- メモリリークなし(遅延削除により期限切れエントリは自然に削減) +- メモリリーク防止(遅延削除により`get`/`count`がアクセスされるエントリは削減されるが、 + 長時間アクセスがないキーは期限切れ後もメモリに残り続ける可能性がある) --- @@ -272,6 +273,10 @@ class TimeLimitedCache { ### 積極削除版(オプション実装) +> [!NOTE] +> 以下のクラスは上記の実装とは別の代替実装です。どちらか一方のみを使用してください。 +> 同一ファイル内で両方を定義すると同名クラスの重複エラーが発生します。 + count() が頻繁に呼ばれる場合の最適化版: ```typescript @@ -284,21 +289,17 @@ class TimeLimitedCache { /** * 期限切れエントリを一括削除 - * @complexity Time: O(n), Space: O(k) where k = 削除対象数 + * @complexity Time: O(n), Space: O(n) where n = 削除対象数(最悪時全エントリ) */ private cleanup(): void { const now = Date.now(); - const keysToDelete: number[] = []; - for (const [key, entry] of this.cache.entries()) { + // MapはforEach中の削除が安全なため、1パスで削除可能 + for (const [key, entry] of this.cache) { if (entry.expiresAt <= now) { - keysToDelete.push(key); + this.cache.delete(key); } } - - for (const key of keysToDelete) { - this.cache.delete(key); - } } set(key: number, value: number, duration: number): boolean { @@ -402,12 +403,15 @@ const hadUnexpiredKey = existingEntry !== undefined && existingEntry.expiresAt > ### パフォーマンス期待値 -| 実装方式 | Runtime | Memory | +| 実装方式 | Runtime(参考値) | Memory(参考値) | | ---------------- | -------------------- | -------------------- | | setTimeout方式 | 54ms (30%) | 54.80MB (74%) | | **遅延削除方式** | **38-42ms (50-60%)** | **53-54MB (75-80%)** | | 積極削除方式 | 40-45ms (45-55%) | 52-53MB (80-85%) | +> [!NOTE] +> 上記の数値は特定の環境・入力での計測結果であり、実行環境により変動します。 + ---

エッジケースと検証観点

@@ -489,7 +493,7 @@ cache.set(1, 100, 'invalid'); // duration must be number ### Q3: メモリリークは発生しないか? -**A**: `get`時に期限切れエントリを遅延削除するため、アクセスされるエントリは自然に削除される。アクセスされないエントリも`count`時に除外されるため、実用上問題なし。 +**A**: `get`時に期限切れエントリを遅延削除するため、アクセスされるエントリは自然に削除される。ただし、**長時間アクセスがないキーは期限切れ後もメモリに残り続ける**可能性がある。LeetCodeの制約(最大100アクション)では問題ないが、本番環境で長期間稼働するアプリケーションでは、周期的な`cleanup()`の実行や積極削除版の採用を検討すべき。 ### Q4: Date.now()の精度は十分か? diff --git a/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README_react.html b/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README_react.html index d3e0fca3..7d8b8ddd 100644 --- a/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README_react.html +++ b/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README_react.html @@ -178,7 +178,7 @@

制約条件

  • 0 ≤ key, value ≤ 109
  • 0 ≤ duration ≤ 1000
  • 1 ≤ actions.length ≤ 100
  • -
  • タイマー管理とメモリリーク防止が必須
  • +
  • 期限切れエントリの適切な処理が必須
  • @@ -954,10 +954,10 @@

    最適化のポイント

    - + @@ -1527,14 +1527,11 @@

    最適化のポイント

    const currentStepData = stepsData.find((s) => s.step === activeStep) || stepsData[0]; + // 自動再生の間隔(ミリ秒) + const AUTO_PLAY_INTERVAL_MS = 2000; + useEffect(() => { if (isPlaying) { - if (activeStep > stepsData.length) { - setIsPlaying(false); - setActiveStep(1); - return; - } - timerRef.current = setTimeout(() => { if (activeStep === stepsData.length) { setActiveStep(1); @@ -1542,7 +1539,7 @@

    最適化のポイント

    } else { setActiveStep((prev) => prev + 1); } - }, 2000); + }, AUTO_PLAY_INTERVAL_MS); } return () => { if (timerRef.current) clearTimeout(timerRef.current); diff --git a/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/Memoize_TS.ipynb b/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/Memoize_TS.ipynb index a3a4c125..14274e49 100644 --- a/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/Memoize_TS.ipynb +++ b/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/Memoize_TS.ipynb @@ -1,190 +1,192 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "9f1d722c", - "metadata": {}, - "source": [ - "## 1. 問題の分析\n", - "\n", - "**競技プログラミング視点での分析**\n", - "\n", - "この問題の本質は「引数の組み合わせをキーとしたキャッシュ(HashMap)の構築」です。引数は順序に敏感であり、`(a, b)` と `(b, a)` は異なるキーとする必要があります。キャッシュのルックアップは O(1) で、最大の懸念点はキー生成の文字列結合コストです。`Map` の文字列キーとして引数を結合すれば、全操作が O(1) に収まります。\n", - "\n", - "`fib` や `factorial` は再帰関数ですが、**LeetCode側がpasses する関数自体は再帰しない**点に注意。つまりmemoize関数が受け取る `fn` はすでに定義された関数であり、内部の再帰がmemoize貫通するかどうかは問題の設定に依存しません。Example 3で `fib(5)` の `getCallCount` が `1` であることが示されているため、**外部から見た呼び出し回数のカウント**で充分です。\n", - "\n", - "**業務開発視点での分析**\n", - "\n", - "型安全性の観点では、引数が `number[]` の可変長であることが最大の課題です。キーの生成には信頼できる区切り文字が必要で、引数そのものが区切り文字と混同されないように設計する必要があります。`Map` を使用し、キーを明確に構築することで保守性も確保できます。\n", - "\n", - "**TypeScript特有の考慮点**\n", - "\n", - "LeetCodeが提供するシグネチャ `type Fn = (...params: number[]) => number` を遵守しつつ、キャッシュの型を明確に定義する。返り関数には `getCallCount` プロパティを付与するが、LeetCode側がこれをどう扱うかは問題の構成に任せ、コアのmemoize logic だけを実装する。\n", - "\n", - "---\n", - "\n", - "## 2. アルゴリズムアプローチ比較\n", - "\n", - "| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |\n", - "|---|---|---|---|---|---|---|\n", - "| **Map + 文字列キー(JSON)** | O(k) キー生成 / O(1) ルーキュップ | O(m) キャッシュエントリ数 | 低 | 高 | 高 | `JSON.stringify` は汎用だが引数がオブジェクトの場合注意要 |\n", - "| **Map + カスタム区切り文字結合** | O(k) キー生成 / O(1) ルーキュップ | O(m) | 最低 | 高 | 最高 | 引数が `number[]` なので区切り文字の衝突なし。最も軽量 |\n", - "| **ネストされたMap(Trie風)** | O(k) 各引数ごと | O(m × k) | 中 | 中 | 低 | 引数が少数で固定なら有効だが過度にコンパレクス |\n", - "\n", - "> `k` = 引数の個数、`m` = キャッシュに格納されたユニーク引数組み合わせ数\n", - "\n", - "---\n", - "\n", - "## 3. 選択したアルゴリズムと理由\n", - "\n", - "**選択したアプローチ**: Map + カスタム区切り文字結合\n", - "\n", - "**理由**:\n", - "- 引数が全て `number` であることが保証されているため、区切り文字 `,` で結合すれば衝突は発生しない(`JSON.stringify` の方が汎用だが、ここでは不要なオーバーヘッド)\n", - "- キャッシュの読み書きが O(1) で、キー生成も引数数に線形\n", - "- `Map` は挿入順を保持し、キーの存在チェックが明確で型安全\n", - "- LeetCodeのシグネチャに最も自然に収まる\n", - "\n", - "---\n", - "\n", - "## 4. 実装コード\n", - "\n", - "```typescript\n", - "// Analyze Complexity\n", - "// Runtime 264 ms\n", - "// Beats 53.64%\n", - "// Memory 96.68 MB\n", - "// Beats 15.19%\n", - "\n", - "type Fn = (...params: number[]) => number;\n", - "\n", - "function memoize(fn: Fn): Fn {\n", - " const cache = new Map();\n", - " let callCount = 0;\n", - "\n", - " const memoized: Fn = function (...args: number[]): number {\n", - " const key = args.join(\",\");\n", - "\n", - " if (cache.has(key)) {\n", - " return cache.get(key)!;\n", - " }\n", - "\n", - " callCount += 1;\n", - " const result = fn(...args);\n", - " cache.set(key, result);\n", - " return result;\n", - " };\n", - "\n", - " (memoized as any).getCallCount = (): number => callCount;\n", - "\n", - " return memoized;\n", - "}\n", - "```\n", - "\n", - "**設計の内訳**:\n", - "\n", - "`args.join(\",\")` がキー生成の核心です。引数が `[2, 2]` なら `\"2,2\"`、`[1, 2]` なら `\"1,2\"` となり、順序に敏感なキーが自然に生まれます。`cache.has(key)` で存在確認を先に行い、ヒットの場合は `fn` を一切呼び出さないことで `callCount` の精度を維持します。\n", - "\n", - "`Map` の選択は `Object` より優れる理由があります。プロトタイプ汚染のリスクがなく、キーの存在確認が `has()` で明確で、数値キーの文字列変換による暗黙の型変換も無いです。\n", - "\n", - "`(memoized as any).getCallCount` は LeetCode の判定ハネス側で使われる拡張プロパティです。型シグネチャ `Fn` に収まらないため `any` キャストが必要ですが、これはLeetCode環境の制約による妥協で、コア logic の型安全性には影響しません。\n", - "\n", - "## 問題の特定\n", - "\n", - "Runtime 264ms・Memory 96.68MB という結果から、主な瓶目標が2つあります。\n", - "\n", - "1. **メモリ 96.68MB(15.19%)** — これが最大の課題。`Map` と文字列キー生成が膨らんでいる。\n", - "2. **Runtime 264ms(53.64%)** — キー生成の文字列結合・`join()` のコストが累積している。\n", - "\n", - "`join(\",\")` は毎呼び出しで新しい文字列オブジェクトを生成し、`Map` もその文字列キーを保持し続けます。引数が `number[]` で制約が明確なのに、文字列という「重い抽象」を使っている点が根本的な損失です。\n", - "\n", - "---\n", - "\n", - "## アプローチ比較(改善案)\n", - "\n", - "| アプローチ | Runtime | Memory | 説明 |\n", - "|---|---|---|---|\n", - "| 現行: `Map` + `join` | O(k) キー生成 | O(m × k) 文字列保持 | 文字列オブジェクト生成・保持が重い |\n", - "| **案A: 数値キー直接エンコード** | O(1) キー計算 | O(m) 数値のみ | `sum` の引数を1つの数値に圧縮 |\n", - "| **案B: ネスト `Map`(2階層)** | O(1) ルーキュップ | O(m) ポインタのみ | 文字列キーを全廃、数値キーで直接インデックス |\n", - "\n", - "引数の制約は以下の通りです。\n", - "- `sum`: `0 <= a, b <= 10^5` → 引数は2つの非負整数\n", - "- `fib`/`factorial`: `1 <= n <= 10` → 引数は1つの整数\n", - "\n", - "これが鍵です。`sum` の引数は最大 `10^5` なので、`a * (10^5 + 1) + b` で**1つの整数に圧縮**できます。これにより文字列キーは完全に廃除されます。\n", - "\n", - "---\n", - "\n", - "## 改善コード\n", - "\n", - "```typescript\n", - "// Analyze Complexity\n", - "// Runtime 235 ms\n", - "// Beats 95.58%\n", - "// Memory 95.88 MB\n", - "// Beats 57.27%\n", - "\n", - "type Fn = (...params: number[]) => number;\n", - "\n", - "function memoize(fn: Fn): Fn {\n", - " // sum: 引数2つ(a, b) → a * 100001 + b で一意な整数キーに圧縮\n", - " // fib/factorial: 引数1つ(n) → nそのもの\n", - " // 両方対応するため、ネスト Map を使用しない。\n", - " // 引数数で分岐し、数値キーのみで Map を構築する。\n", - " const cache = new Map();\n", - " let callCount = 0;\n", - "\n", - " const memoized: Fn = function (...args: number[]): number {\n", - " // 引数が1つなら n そのもの、2つなら圧縮キー\n", - " const key = args.length === 1\n", - " ? args[0]\n", - " : args[0] * 100001 + args[1];\n", - "\n", - " if (cache.has(key)) {\n", - " return cache.get(key)!;\n", - " }\n", - "\n", - " callCount += 1;\n", - " const result = fn(...args);\n", - " cache.set(key, result);\n", - " return result;\n", - " };\n", - "\n", - " (memoized as any).getCallCount = (): number => callCount;\n", - "\n", - " return memoized;\n", - "}\n", - "```\n", - "\n", - "---\n", - "\n", - "## 改善の詳細\n", - "\n", - "**キー圧縮の正当性の確認です。**\n", - "\n", - "`a * 100001 + b` で衝突しないことを検証します。異なる `(a1, b1)` と `(a2, b2)` があって同じキーを生成したとすると:\n", - "\n", - "```\n", - "a1 * 100001 + b1 === a2 * 100001 + b2\n", - "→ (a1 - a2) * 100001 === b2 - b1\n", - "```\n", - "\n", - "`b` の範囲が `0 ~ 10^5` なので `|b2 - b1| <= 10^5 < 100001` です。よって左辺が `100001` の倍数になるためには `a1 === a2` が必要で、それは `b1 === b2` を意味します。つまり衝突は不可能に proven されます。\n", - "\n", - "**何が変わったかの整理です。**\n", - "\n", - "現行コードでは、呼び出しのたびに `args.join(\",\")` が新しい文字列オブジェクトを確保し、その文字列が `Map` のキーとして永続保持されました。改善版では引数を一つの数値に圧縮し、`Map` で管理します。文字列オブジェクトの生成がゼロに、キーの保持も数値(8バイト)に圧縮されます。これがメモリの大幅削減とRuntimeの改善の両方に直結します。\n", - "\n", - "**1つの引数の場合**(`fib`/`factorial`)では `n` は最大 `10` なので、キーをそのまま使うことで圧縮演算自体も廃除されます。" - ] - } - ], - "metadata": { - "language_info": { - "name": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} + "cells": [ + { + "cell_type": "markdown", + "id": "9f1d722c", + "metadata": {}, + "source": [ + "## 1. 問題の分析\n", + "\n", + "**競技プログラミング視点での分析**\n", + "\n", + "この問題の本質は「引数の組み合わせをキーとしたキャッシュ(HashMap)の構築」です。引数は順序に敏感であり、`(a, b)` と `(b, a)` は異なるキーとする必要があります。キャッシュのルックアップは O(1) で、最大の懸念点はキー生成の文字列結合コストです。`Map` の文字列キーとして引数を結合すれば、全操作が O(1) に収まります。\n", + "\n", + "`fib` や `factorial` は再帰関数ですが、**LeetCode側がpasses する関数自体は再帰しない**点に注意。つまりmemoize関数が受け取る `fn` はすでに定義された関数であり、内部の再帰がmemoize貫通するかどうかは問題の設定に依存しません。Example 3で `fib(5)` の `getCallCount` が `1` であることが示されているため、**外部から見た呼び出し回数のカウント**で充分です。\n", + "\n", + "**業務開発視点での分析**\n", + "\n", + "型安全性の観点では、引数が `number[]` の可変長であることが最大の課題です。キーの生成には信頼できる区切り文字が必要で、引数そのものが区切り文字と混同されないように設計する必要があります。`Map` を使用し、キーを明確に構築することで保守性も確保できます。\n", + "\n", + "**TypeScript特有の考慮点**\n", + "\n", + "LeetCodeが提供するシグネチャ `type Fn = (...params: number[]) => number` を遵守しつつ、キャッシュの型を明確に定義する。返り関数には `getCallCount` プロパティを付与するが、LeetCode側がこれをどう扱うかは問題の構成に任せ、コアのmemoize logic だけを実装する。\n", + "\n", + "---\n", + "\n", + "## 2. アルゴリズムアプローチ比較\n", + "\n", + "| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |\n", + "|---|---|---|---|---|---|---|\n", + "| **Map + 文字列キー(JSON)** | O(k) キー生成 / O(1) ルーキュップ | O(m) キャッシュエントリ数 | 低 | 高 | 高 | `JSON.stringify` は汎用だが引数がオブジェクトの場合注意要 |\n", + "| **Map + カスタム区切り文字結合** | O(k) キー生成 / O(1) ルーキュップ | O(m) | 最低 | 高 | 最高 | 引数が `number[]` なので区切り文字の衝突なし。最も軽量 |\n", + "| **ネストされたMap(Trie風)** | O(k) 各引数ごと | O(m × k) | 中 | 中 | 低 | 引数が少数で固定なら有効だが過度にコンパレクス |\n", + "\n", + "> `k` = 引数の個数、`m` = キャッシュに格納されたユニーク引数組み合わせ数\n", + "\n", + "---\n", + "\n", + "## 3. 選択したアルゴリズムと理由\n", + "\n", + "**選択したアプローチ**: Map + カスタム区切り文字結合\n", + "\n", + "**理由**:\n", + "- 引数が全て `number` であることが保証されているため、区切り文字 `,` で結合すれば衝突は発生しない(`JSON.stringify` の方が汎用だが、ここでは不要なオーバーヘッド)\n", + "- キャッシュの読み書きが O(1) で、キー生成も引数数に線形\n", + "- `Map` は挿入順を保持し、キーの存在チェックが明確で型安全\n", + "- LeetCodeのシグネチャに最も自然に収まる\n", + "\n", + "---\n", + "\n", + "## 4. 実装コード\n", + "\n", + "```typescript\n", + "// Analyze Complexity\n", + "// Runtime 264 ms\n", + "// Beats 53.64%\n", + "// Memory 96.68 MB\n", + "// Beats 15.19%\n", + "\n", + "type Fn = (...params: number[]) => number;\n", + "\n", + "function memoize(fn: Fn): Fn {\n", + " const cache = new Map();\n", + " let callCount = 0;\n", + "\n", + " const memoized: Fn = function (...args: number[]): number {\n", + " const key = args.join(\",\");\n", + "\n", + " const cached = cache.get(key);\n", + " if (cached !== undefined) {\n", + " return cached;\n", + " }\n", + "\n", + " callCount += 1;\n", + " const result = fn(...args);\n", + " cache.set(key, result);\n", + " return result;\n", + " };\n", + "\n", + " (memoized as any).getCallCount = (): number => callCount;\n", + "\n", + " return memoized;\n", + "}\n", + "```\n", + "\n", + "**設計の内訳**:\n", + "\n", + "`args.join(\",\")` がキー生成の核心です。引数が `[2, 2]` なら `\"2,2\"`、`[1, 2]` なら `\"1,2\"` となり、順序に敏感なキーが自然に生まれます。`cache.has(key)` で存在確認を先に行い、ヒットの場合は `fn` を一切呼び出さないことで `callCount` の精度を維持します。\n", + "\n", + "`Map` の選択は `Object` より優れる理由があります。プロトタイプ汚染のリスクがなく、キーの存在確認が `has()` で明確で、数値キーの文字列変換による暗黙の型変換も無いです。\n", + "\n", + "`(memoized as any).getCallCount` は LeetCode の判定ハネス側で使われる拡張プロパティです。型シグネチャ `Fn` に収まらないため `any` キャストが必要ですが、これはLeetCode環境の制約による妥協で、コア logic の型安全性には影響しません。\n", + "\n", + "## 問題の特定\n", + "\n", + "Runtime 264ms・Memory 96.68MB という結果から、主な瓶目標が2つあります。\n", + "\n", + "1. **メモリ 96.68MB(15.19%)** — これが最大の課題。`Map` と文字列キー生成が膨らんでいる。\n", + "2. **Runtime 264ms(53.64%)** — キー生成の文字列結合・`join()` のコストが累積している。\n", + "\n", + "`join(\",\")` は毎呼び出しで新しい文字列オブジェクトを生成し、`Map` もその文字列キーを保持し続けます。引数が `number[]` で制約が明確なのに、文字列という「重い抽象」を使っている点が根本的な損失です。\n", + "\n", + "---\n", + "\n", + "## アプローチ比較(改善案)\n", + "\n", + "| アプローチ | Runtime | Memory | 説明 |\n", + "|---|---|---|---|\n", + "| 現行: `Map` + `join` | O(k) キー生成 | O(m × k) 文字列保持 | 文字列オブジェクト生成・保持が重い |\n", + "| **案A: 数値キー直接エンコード** | O(1) キー計算 | O(m) 数値のみ | `sum` の引数を1つの数値に圧縮 |\n", + "| **案B: ネスト `Map`(2階層)** | O(1) ルーキュップ | O(m) ポインタのみ | 文字列キーを全廃、数値キーで直接インデックス |\n", + "\n", + "引数の制約は以下の通りです。\n", + "- `sum`: `0 <= a, b <= 10^5` → 引数は2つの非負整数\n", + "- `fib`/`factorial`: `1 <= n <= 10` → 引数は1つの整数\n", + "\n", + "これが鍵です。`sum` の引数は最大 `10^5` なので、`a * (10^5 + 1) + b` で**1つの整数に圧縮**できます。これにより文字列キーは完全に廃除されます。\n", + "\n", + "---\n", + "\n", + "## 改善コード\n", + "\n", + "```typescript\n", + "// Analyze Complexity\n", + "// Runtime 235 ms\n", + "// Beats 95.58%\n", + "// Memory 95.88 MB\n", + "// Beats 57.27%\n", + "\n", + "type Fn = (...params: number[]) => number;\n", + "\n", + "function memoize(fn: Fn): Fn {\n", + " // sum: 引数2つ(a, b) → a * 100001 + b で一意な整数キーに圧縮\n", + " // fib/factorial: 引数1つ(n) → nそのもの\n", + " // 両方対応するため、ネスト Map を使用しない。\n", + " // 引数数で分岐し、数値キーのみで Map を構築する。\n", + " const cache = new Map();\n", + " let callCount = 0;\n", + "\n", + " const memoized: Fn = function (...args: number[]): number {\n", + " // 引数が1つなら n そのもの、2つなら圧縮キー\n", + " const key = args.length === 1\n", + " ? args[0]\n", + " : args[0] * 100001 + args[1];\n", + "\n", + " const cached = cache.get(key);\n", + " if (cached !== undefined) {\n", + " return cached;\n", + " }\n", + "\n", + " callCount += 1;\n", + " const result = fn(...args);\n", + " cache.set(key, result);\n", + " return result;\n", + " };\n", + "\n", + " (memoized as any).getCallCount = (): number => callCount;\n", + "\n", + " return memoized;\n", + "}\n", + "```\n", + "\n", + "---\n", + "\n", + "## 改善の詳細\n", + "\n", + "**キー圧縮の正当性の確認です。**\n", + "\n", + "`a * 100001 + b` で衝突しないことを検証します。異なる `(a1, b1)` と `(a2, b2)` があって同じキーを生成したとすると:\n", + "\n", + "```\n", + "a1 * 100001 + b1 === a2 * 100001 + b2\n", + "→ (a1 - a2) * 100001 === b2 - b1\n", + "```\n", + "\n", + "`b` の範囲が `0 ~ 10^5` なので `|b2 - b1| <= 10^5 < 100001` です。よって左辺が `100001` の倍数になるためには `a1 === a2` が必要で、それは `b1 === b2` を意味します。つまり衝突は不可能に proven されます。\n", + "\n", + "**何が変わったかの整理です。**\n", + "\n", + "現行コードでは、呼び出しのたびに `args.join(\",\")` が新しい文字列オブジェクトを確保し、その文字列が `Map` のキーとして永続保持されました。改善版では引数を一つの数値に圧縮し、`Map` で管理します。文字列オブジェクトの生成がゼロに、キーの保持も数値(8バイト)に圧縮されます。これがメモリの大幅削減とRuntimeの改善の両方に直結します。\n", + "\n", + "**1つの引数の場合**(`fib`/`factorial`)では `n` は最大 `10` なので、キーをそのまま使うことで圧縮演算自体も廃除されます。" + ] + } + ], + "metadata": { + "language_info": { + "name": "typescript" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/README.md b/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/README.md index 5a370d9e..8cf50c5c 100644 --- a/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/README.md +++ b/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/README.md @@ -35,9 +35,10 @@

    アルゴリズム要点 TL;DR

    - **戦略**: 引数を単一の整数キーに圧縮し、`Map` で O(1) キャッシュ -- **キー設計**: +- **キー設計(固定アリティ前提)**: - 引数1つの場合: キー = `n` そのもの - 引数2つの場合: キー = `a * 100001 + b`(圧縮キー) + - ※ 同一 memoize で可変長(1/2引数混在)を許すとキー衡突が起こり得るため不可 - **データ構造**: `Map`(キー: 圧縮整数、値: キャッシュ結果) - **計算量**: Time O(1) per call / Space O(m) — `m` はキャッシュエントリ数 - **メモリ設計**: 文字列キーを完全に廃除し、数値キーのみで構築 @@ -114,7 +115,7 @@ a1 * 100001 + b1 = a2 * 100001 + b2 ### 4. 終了性 -キャッシュのルーキュップと数値演算は定常時間で終了する。無限ループは発生しない。 +キャッシュのルックアップと数値演算は定常時間で終了する。無限ループは発生しない。 --- @@ -123,7 +124,7 @@ a1 * 100001 + b1 = a2 * 100001 + b2 | 操作 | 時間計算量 | 空間計算量 | 備考 | | ------------------ | -------------- | ---------- | ------------------------------ | | キー計算 | O(1) | O(1) | 乗算・加算のみ | -| `Map` ルーキュップ | O(1) 平均 | — | ハッシュテーブル | +| `Map` ルックアップ | O(1) 平均 | — | ハッシュテーブル | | キャッシュ保持 | — | O(m) | `m` = ユニーク引数組み合わせ数 | | 呼び出し全体 | O(1) amortized | O(m) | `fn` の実行コストは含まない | @@ -157,7 +158,7 @@ function memoize(fn: Fn): Fn { // 100001 = 10^5 + 1 で、a と b の組み合わせが一意に対応 const key = args.length === 1 ? args[0] : args[0] * 100001 + args[1]; - // キャッシュヒット: fn を呼び出せず結果を返す + // キャッシュヒット: fn を呼び出さず結果を返す if (cache.has(key)) { return cache.get(key)!; } @@ -169,7 +170,7 @@ function memoize(fn: Fn): Fn { return result; }; - // LeetCode の判定ハネス側から呼ばれる拡張プロパティ + // LeetCode の判定ハーネス側から呼ばれる拡張プロパティ // Fn 型には収まらないため any キャスト(コア logic には影響なし) (memoized as any).getCallCount = (): number => callCount; @@ -232,7 +233,7 @@ const cache = new Map(); // キーの永続保持がメモリ圧 **Q: なぜ `(memoized as any)` キャスト が必要なのか?** -LeetCode側の判定ハネスが `getCallCount()` プロパティを期待するが、`type Fn` の定義にはこのプロパティが含まれない。`any` キャストは環境の制約による妥協であり、キャッシュロジック自体の型安全性には影響しない。 +LeetCode側の判定ハーネスが `getCallCount()` プロパティを期待するが、`type Fn` の定義にはこのプロパティが含まれない。`any` キャストは環境の制約による妥協であり、キャッシュロジック自体の型安全性には影響しない。 **Q: `fib` や `factorial` の内部再帰もメモイズされるのか?** From 2fd2e57f15a859c42a0aad3a84ee8f39b22bd296 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Sun, 1 Feb 2026 14:05:39 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E3=83=AC=E3=83=93=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=BC=E3=83=89=E3=83=90=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=81=AB=E5=9F=BA=E3=81=A5=E3=81=8F=E8=BF=BD=E5=8A=A0=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 修正内容 ### Space複雑度の正確化 - cleanup()はインプレース削除のため Space: O(n) → O(1) に修正 - CacheWithTimeLimit_TS.ipynb - README.md (2622) ### Pythonバージョン情報の修正 - asyncio.get_running_loop() は Python 3.7+ で利用可能 - コメントを "3.10+推奨" → "3.7+で推奨" に修正 - README.md (2621) ### 可読性優先の実装変更 - cache.has() + cache.get()! パターンに戻す (2箇所) - パフォーマンスより明示性を優先 - Memoize_TS.ipynb --- .../Claude Code Sonnet 4.5/README.md | 2 +- .../CacheWithTimeLimit_TS.ipynb | 810 +++++++++--------- .../Claude Code Sonnet 4.5/README.md | 2 +- .../Claude Code Sonnet 4.5/Memoize_TS.ipynb | 10 +- 4 files changed, 411 insertions(+), 413 deletions(-) diff --git a/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/README.md b/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/README.md index 30d12930..8db6cd5b 100644 --- a/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/README.md +++ b/JavaScript/2621. Sleep/Claude Code Sonnet 4.5/README.md @@ -189,7 +189,7 @@ async def sleep(millis: int) -> None: raise ValueError("millis must be an integer between 1 and 1000") # イベントループ取得 - loop = asyncio.get_running_loop() # 実行中のイベントループ取得(Python 3.10+推奨) + loop = asyncio.get_running_loop() # 実行中のイベントループ取得(Python 3.7+で推奨) # Future 作成(コルーチンが待機するオブジェクト) future: asyncio.Future[None] = loop.create_future() diff --git a/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/CacheWithTimeLimit_TS.ipynb b/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/CacheWithTimeLimit_TS.ipynb index 49504fa3..cc8c21c6 100644 --- a/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/CacheWithTimeLimit_TS.ipynb +++ b/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/CacheWithTimeLimit_TS.ipynb @@ -1,407 +1,407 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "01d48058", - "metadata": {}, - "source": [ - "# TypeScript コーディング問題解答\n", - "\n", - "## 問題文\n", - "有効期限付きキャッシュクラスの実装。各キーに有効期限を設定し、期限切れのキーは自動的にアクセス不可になる。\n", - "\n", - "## 1. 問題の分析\n", - "\n", - "### 競技プログラミング視点での分析\n", - "- **実行速度最優先**: Map構造でO(1)アクセス、タイムアウトIDの直接管理\n", - "- **メモリ使用量最小化**: 期限切れエントリの即座削除、不要なオブジェクト生成回避\n", - "- **最適化ポイント**: タイマー管理の効率化、重複処理の排除\n", - "\n", - "### 業務開発視点での分析\n", - "- **型安全性**: 厳格な型定義でコンパイル時エラー防止\n", - "- **保守性**: 明確な責務分離、分かりやすいメソッド名\n", - "- **エラーハンドリング**: 入力値の検証、境界値の適切な処理\n", - "- **メモリリーク防止**: タイマーの適切なクリーンアップ\n", - "\n", - "### TypeScript特有の考慮点\n", - "- **型推論の活用**: Mapで型安全性を確保\n", - "- **readonly修飾子**: 不変性の保証\n", - "- **インターフェース定義**: 内部構造の明確化\n", - "- **null安全性**: undefined/nullチェックの適切な実施\n", - "\n", - "## 2. アルゴリズムアプローチ比較\n", - "\n", - "| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |\n", - "|---------|----------|----------|------------|---------|--------|------|\n", - "| Map + setTimeout | O(1) | O(n) | 低 | 高 | 高 | 最適解 |\n", - "| 配列 + 線形探索 | O(n) | O(n) | 低 | 高 | 中 | get/setが遅い |\n", - "| 都度チェック方式 | O(1) | O(n) | 中 | 中 | 中 | タイマー不要だがgetで毎回チェック |\n", - "\n", - "## 3. 選択したアルゴリズムと理由\n", - "\n", - "### 選択したアプローチ\n", - "**Map + setTimeout方式**\n", - "\n", - "### 理由\n", - "- **計算量的な優位性**: すべての操作がO(1)で実行可能\n", - "- **TypeScript環境での型安全性**: Map型により強力な型推論が効く\n", - "- **保守性・可読性の観点**: Mapの標準メソッドで意図が明確\n", - "\n", - "### TypeScript特有の最適化ポイント\n", - "- **型推論**: Mapのジェネリクス型で完全な型安全性\n", - "- **インターフェース**: CacheEntry型で構造を明確化\n", - "- **strictモード**: コンパイル時にnull/undefinedを厳密チェック\n", - "- **NodeJS.Timeout型**: タイマーIDの型安全な管理\n", - "\n", - "## 4. 実装コード\n", - "\n", - "```typescript\n", - "// Analyze Complexity\n", - "// Runtime 54 ms\n", - "// Beats 30.18%\n", - "// Memory 54.80 MB\n", - "// Beats 74.74%\n", - "/**\n", - " * キャッシュエントリの内部構造\n", - " */\n", - "interface CacheEntry {\n", - " readonly value: number;\n", - " readonly timeoutId: NodeJS.Timeout;\n", - "}\n", - "\n", - "/**\n", - " * 有効期限付きキャッシュクラス\n", - " * @description キーごとに有効期限を設定し、期限切れで自動削除されるキャッシュ\n", - " * @complexity \n", - " * - set: Time O(1), Space O(1)\n", - " * - get: Time O(1), Space O(1)\n", - " * - count: Time O(1), Space O(1)\n", - " */\n", - "class TimeLimitedCache {\n", - " private cache: Map;\n", - " \n", - " constructor() {\n", - " this.cache = new Map();\n", - " }\n", - " \n", - " /**\n", - " * キーと値を設定し、有効期限を指定する\n", - " * @param key - キー (0 <= key <= 10^9)\n", - " * @param value - 値 (0 <= value <= 10^9)\n", - " * @param duration - 有効期限(ミリ秒、0 <= duration <= 1000)\n", - " * @returns 既存の未期限切れキーが存在した場合true、それ以外false\n", - " * @complexity Time: O(1), Space: O(1)\n", - " */\n", - " set(key: number, value: number, duration: number): boolean {\n", - " // 型安全性: TypeScriptの型システムで保証されているが、\n", - " // 実行時の追加検証は省略(LeetCode環境では制約条件が保証される)\n", - " \n", - " const existingEntry = this.cache.get(key);\n", - " let hadKey = false;\n", - " \n", - " // 既存エントリが存在する場合、タイマーをクリア\n", - " if (existingEntry !== undefined) {\n", - " clearTimeout(existingEntry.timeoutId);\n", - " hadKey = true;\n", - " }\n", - " \n", - " // 新しいタイマーを設定\n", - " const timeoutId = setTimeout(() => {\n", - " this.cache.delete(key);\n", - " }, duration);\n", - " \n", - " // 新しいエントリを保存\n", - " this.cache.set(key, {\n", - " value,\n", - " timeoutId\n", - " });\n", - " \n", - " return hadKey;\n", - " }\n", - " \n", - " /**\n", - " * キーに対応する値を取得\n", - " * @param key - 取得するキー\n", - " * @returns 未期限切れのキーが存在すれば対応する値、存在しなければ-1\n", - " * @complexity Time: O(1), Space: O(1)\n", - " */\n", - " get(key: number): number {\n", - " const entry = this.cache.get(key);\n", - " \n", - " // undefinedチェック(型安全)\n", - " if (entry === undefined) {\n", - " return -1;\n", - " }\n", - " \n", - " return entry.value;\n", - " }\n", - " \n", - " /**\n", - " * 未期限切れキーの数を取得\n", - " * @returns アクティブなキーの数\n", - " * @complexity Time: O(1), Space: O(1)\n", - " */\n", - " count(): number {\n", - " return this.cache.size;\n", - " }\n", - "}\n", - "\n", - "/**\n", - " * const timeLimitedCache = new TimeLimitedCache()\n", - " * timeLimitedCache.set(1, 42, 1000); // false\n", - " * timeLimitedCache.get(1) // 42\n", - " * timeLimitedCache.count() // 1\n", - " */\n", - "```\n", - "\n", - "## 5. TypeScript固有の最適化観点\n", - "\n", - "### 型安全性の活用\n", - "\n", - "1. **コンパイル時エラー防止**\n", - " - `Map`で型が完全に推論される\n", - " - `entry?.value`のようなオプショナルチェーンも可能だが、明示的な`undefined`チェックで意図を明確化\n", - "\n", - "2. **インターフェース定義**\n", - " - `CacheEntry`インターフェースで内部構造を型レベルで文書化\n", - " - `readonly`修飾子でイミュータビリティを表現\n", - "\n", - "3. **null/undefined安全性**\n", - " - `Map.get()`の戻り値が`CacheEntry | undefined`と正確に型付け\n", - " - strictNullChecksにより安全なチェック強制\n", - "\n", - "### 実装上の工夫\n", - "\n", - "1. **メモリリーク防止**\n", - " - `clearTimeout()`で既存タイマーを確実にクリア\n", - " - `setTimeout`のコールバックで自動削除\n", - "\n", - "2. **パフォーマンス最適化**\n", - " - Mapの特性を活かしたO(1)操作\n", - " - 不要なオブジェクト生成を回避\n", - "\n", - "3. **可読性とメンテナンス性**\n", - " - メソッド名が意図を明確に表現\n", - " - JSDocコメントで使用方法を文書化\n", - " - 計算量をコメントで明示\n", - "\n", - "# パフォーマンス改善版の実装\n", - "\n", - "現在の実装は **Runtime 54ms (30.18%)** ということで、改善の余地があります。主な最適化ポイントを分析して改善版を提示します。\n", - "\n", - "## 改善ポイントの分析\n", - "\n", - "### 現在の実装の問題点\n", - "\n", - "1. **setTimeout/clearTimeoutのオーバーヘッド**\n", - " - 各`set`呼び出しで必ずタイマー操作が発生\n", - " - 既存エントリの`clearTimeout`→新規`setTimeout`の2回の呼び出し\n", - "\n", - "2. **Map操作の回数**\n", - " - `get` → `delete` → `set`と複数回のMap操作\n", - "\n", - "3. **オブジェクト生成コスト**\n", - " - 毎回新しい`CacheEntry`オブジェクトを生成\n", - "\n", - "### 最適化戦略\n", - "\n", - "1. **タイマー処理の最適化**: 期限時刻を保存し、遅延チェック方式に変更\n", - "2. **Map操作の削減**: 単一の更新操作にまとめる\n", - "3. **オブジェクト再利用**: 構造を単純化\n", - "\n", - "## 改善版実装\n", - "\n", - "```typescript\n", - "// Analyze Complexity\n", - "// Runtime 49 ms\n", - "// Beats 55.44%\n", - "// Memory 55.56 MB\n", - "// Beats 27.72%\n", - "/**\n", - " * キャッシュエントリの内部構造(最適化版)\n", - " * - タイマーIDの代わりに期限時刻を保存\n", - " * - よりシンプルな構造で高速化\n", - " */\n", - "interface CacheEntry {\n", - " value: number;\n", - " expiresAt: number; // 期限時刻(ミリ秒)\n", - "}\n", - "\n", - "/**\n", - " * 有効期限付きキャッシュクラス(最適化版)\n", - " * @description 遅延削除方式で高速化を実現\n", - " * @complexity \n", - " * - set: Time O(1), Space O(1) - タイマー操作なし\n", - " * - get: Time O(1), Space O(1) - 期限チェックのみ\n", - " * - count: Time O(n), Space O(1) - 期限切れを除外してカウント\n", - " */\n", - "class TimeLimitedCache {\n", - " private cache: Map;\n", - " \n", - " constructor() {\n", - " this.cache = new Map();\n", - " }\n", - " \n", - " /**\n", - " * キーと値を設定し、有効期限を指定する\n", - " * @param key - キー\n", - " * @param value - 値\n", - " * @param duration - 有効期限(ミリ秒)\n", - " * @returns 既存の未期限切れキーが存在した場合true、それ以外false\n", - " * @complexity Time: O(1), Space: O(1)\n", - " */\n", - " set(key: number, value: number, duration: number): boolean {\n", - " const now = Date.now();\n", - " const expiresAt = now + duration;\n", - " \n", - " const existingEntry = this.cache.get(key);\n", - " \n", - " // 既存エントリが存在し、かつ未期限切れかチェック\n", - " const hadUnexpiredKey = existingEntry !== undefined && existingEntry.expiresAt > now;\n", - " \n", - " // 新しいエントリを設定(直接更新、タイマー不要)\n", - " this.cache.set(key, { value, expiresAt });\n", - " \n", - " return hadUnexpiredKey;\n", - " }\n", - " \n", - " /**\n", - " * キーに対応する値を取得\n", - " * @param key - 取得するキー\n", - " * @returns 未期限切れのキーが存在すれば対応する値、存在しなければ-1\n", - " * @complexity Time: O(1), Space: O(1)\n", - " */\n", - " get(key: number): number {\n", - " const entry = this.cache.get(key);\n", - " \n", - " if (entry === undefined) {\n", - " return -1;\n", - " }\n", - " \n", - " // 期限切れチェック\n", - " if (entry.expiresAt <= Date.now()) {\n", - " this.cache.delete(key); // 遅延削除\n", - " return -1;\n", - " }\n", - " \n", - " return entry.value;\n", - " }\n", - " \n", - " /**\n", - " * 未期限切れキーの数を取得\n", - " * @returns アクティブなキーの数\n", - " * @complexity Time: O(n), Space: O(n)\n", - " */\n", - " count(): number {\n", - " const now = Date.now();\n", - " let count = 0;\n", - " \n", - " // 期限切れでないエントリのみカウント\n", - " for (const entry of this.cache.values()) {\n", - " if (entry.expiresAt > now) {\n", - " count++;\n", - " }\n", - " }\n", - " \n", - " return count;\n", - " }\n", - "}\n", - "```\n", - "\n", - "## さらなる最適化版(積極的クリーンアップ付き)\n", - "\n", - "`count()`の呼び出しが多い場合はこちらの方が高速です:\n", - "\n", - "```typescript\n", - "// Analyze Complexity\n", - "// Runtime 49 ms\n", - "// Beats 55.44%\n", - "// Memory 55.31 MB\n", - "// Beats 43.33%\n", - "/**\n", - " * 有効期限付きキャッシュクラス(積極的クリーンアップ版)\n", - " */\n", - "class TimeLimitedCache {\n", - " private cache: Map;\n", - " \n", - " constructor() {\n", - " this.cache = new Map();\n", - " }\n", - " \n", - " /**\n", - " * 期限切れエントリを削除(内部ヘルパー)\n", - " * @complexity Time: O(n), Space: O(n)\n", - " */\n", - " private cleanup(): void {\n", - " const now = Date.now();\n", - " \n", - " for (const [key, entry] of this.cache) {\n", - " if (entry.expiresAt <= now) {\n", - " this.cache.delete(key);\n", - " }\n", - " }\n", - " }\n", - " \n", - " set(key: number, value: number, duration: number): boolean {\n", - " const now = Date.now();\n", - " const expiresAt = now + duration;\n", - " \n", - " const existingEntry = this.cache.get(key);\n", - " const hadUnexpiredKey = existingEntry !== undefined && existingEntry.expiresAt > now;\n", - " \n", - " this.cache.set(key, { value, expiresAt });\n", - " \n", - " return hadUnexpiredKey;\n", - " }\n", - " \n", - " get(key: number): number {\n", - " const entry = this.cache.get(key);\n", - " \n", - " if (entry === undefined) {\n", - " return -1;\n", - " }\n", - " \n", - " if (entry.expiresAt <= Date.now()) {\n", - " this.cache.delete(key);\n", - " return -1;\n", - " }\n", - " \n", - " return entry.value;\n", - " }\n", - " \n", - " count(): number {\n", - " this.cleanup(); // count時に一括クリーンアップ\n", - " return this.cache.size;\n", - " }\n", - "}\n", - "```\n", - "\n", - "## パフォーマンス比較\n", - "\n", - "| 実装方式 | set | get | count | メモリ | 備考 |\n", - "|---------|-----|-----|-------|--------|------|\n", - "| 元実装(setTimeout) | O(1) + タイマー | O(1) | O(1) | 良 | タイマーオーバーヘッド大 |\n", - "| 遅延削除版 | O(1) | O(1) | O(n) | 良 | タイマー不要で高速 |\n", - "| 積極削除版 | O(1) | O(1) | O(n) | 最良 | count時に自動クリーンアップ |\n", - "\n", - "## 推奨実装\n", - "\n", - "**遅延削除版(最初の改善版)** を推奨します。理由:\n", - "\n", - "1. **Runtime改善**: setTimeout/clearTimeoutの完全排除で30-40%高速化が期待できる\n", - "2. **シンプル**: 実装が明快で保守性が高い\n", - "3. **メモリ効率**: 既に74.74%と良好なメモリ使用量を維持\n", - "4. **実用的**: LeetCodeの制約条件(最大100アクション)では`count`のO(n)は問題なし\n", - "\n", - "この実装で **Runtime 40ms以下、上位50%以上** を狙えるはずです!" - ] - } - ], - "metadata": { - "language_info": { - "name": "typescript" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "cells": [ + { + "cell_type": "markdown", + "id": "01d48058", + "metadata": {}, + "source": [ + "# TypeScript コーディング問題解答\n", + "\n", + "## 問題文\n", + "有効期限付きキャッシュクラスの実装。各キーに有効期限を設定し、期限切れのキーは自動的にアクセス不可になる。\n", + "\n", + "## 1. 問題の分析\n", + "\n", + "### 競技プログラミング視点での分析\n", + "- **実行速度最優先**: Map構造でO(1)アクセス、タイムアウトIDの直接管理\n", + "- **メモリ使用量最小化**: 期限切れエントリの即座削除、不要なオブジェクト生成回避\n", + "- **最適化ポイント**: タイマー管理の効率化、重複処理の排除\n", + "\n", + "### 業務開発視点での分析\n", + "- **型安全性**: 厳格な型定義でコンパイル時エラー防止\n", + "- **保守性**: 明確な責務分離、分かりやすいメソッド名\n", + "- **エラーハンドリング**: 入力値の検証、境界値の適切な処理\n", + "- **メモリリーク防止**: タイマーの適切なクリーンアップ\n", + "\n", + "### TypeScript特有の考慮点\n", + "- **型推論の活用**: Mapで型安全性を確保\n", + "- **readonly修飾子**: 不変性の保証\n", + "- **インターフェース定義**: 内部構造の明確化\n", + "- **null安全性**: undefined/nullチェックの適切な実施\n", + "\n", + "## 2. アルゴリズムアプローチ比較\n", + "\n", + "| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |\n", + "|---------|----------|----------|------------|---------|--------|------|\n", + "| Map + setTimeout | O(1) | O(n) | 低 | 高 | 高 | 最適解 |\n", + "| 配列 + 線形探索 | O(n) | O(n) | 低 | 高 | 中 | get/setが遅い |\n", + "| 都度チェック方式 | O(1) | O(n) | 中 | 中 | 中 | タイマー不要だがgetで毎回チェック |\n", + "\n", + "## 3. 選択したアルゴリズムと理由\n", + "\n", + "### 選択したアプローチ\n", + "**Map + setTimeout方式**\n", + "\n", + "### 理由\n", + "- **計算量的な優位性**: すべての操作がO(1)で実行可能\n", + "- **TypeScript環境での型安全性**: Map型により強力な型推論が効く\n", + "- **保守性・可読性の観点**: Mapの標準メソッドで意図が明確\n", + "\n", + "### TypeScript特有の最適化ポイント\n", + "- **型推論**: Mapのジェネリクス型で完全な型安全性\n", + "- **インターフェース**: CacheEntry型で構造を明確化\n", + "- **strictモード**: コンパイル時にnull/undefinedを厳密チェック\n", + "- **NodeJS.Timeout型**: タイマーIDの型安全な管理\n", + "\n", + "## 4. 実装コード\n", + "\n", + "```typescript\n", + "// Analyze Complexity\n", + "// Runtime 54 ms\n", + "// Beats 30.18%\n", + "// Memory 54.80 MB\n", + "// Beats 74.74%\n", + "/**\n", + " * キャッシュエントリの内部構造\n", + " */\n", + "interface CacheEntry {\n", + " readonly value: number;\n", + " readonly timeoutId: NodeJS.Timeout;\n", + "}\n", + "\n", + "/**\n", + " * 有効期限付きキャッシュクラス\n", + " * @description キーごとに有効期限を設定し、期限切れで自動削除されるキャッシュ\n", + " * @complexity \n", + " * - set: Time O(1), Space O(1)\n", + " * - get: Time O(1), Space O(1)\n", + " * - count: Time O(1), Space O(1)\n", + " */\n", + "class TimeLimitedCache {\n", + " private cache: Map;\n", + " \n", + " constructor() {\n", + " this.cache = new Map();\n", + " }\n", + " \n", + " /**\n", + " * キーと値を設定し、有効期限を指定する\n", + " * @param key - キー (0 <= key <= 10^9)\n", + " * @param value - 値 (0 <= value <= 10^9)\n", + " * @param duration - 有効期限(ミリ秒、0 <= duration <= 1000)\n", + " * @returns 既存の未期限切れキーが存在した場合true、それ以外false\n", + " * @complexity Time: O(1), Space: O(1)\n", + " */\n", + " set(key: number, value: number, duration: number): boolean {\n", + " // 型安全性: TypeScriptの型システムで保証されているが、\n", + " // 実行時の追加検証は省略(LeetCode環境では制約条件が保証される)\n", + " \n", + " const existingEntry = this.cache.get(key);\n", + " let hadKey = false;\n", + " \n", + " // 既存エントリが存在する場合、タイマーをクリア\n", + " if (existingEntry !== undefined) {\n", + " clearTimeout(existingEntry.timeoutId);\n", + " hadKey = true;\n", + " }\n", + " \n", + " // 新しいタイマーを設定\n", + " const timeoutId = setTimeout(() => {\n", + " this.cache.delete(key);\n", + " }, duration);\n", + " \n", + " // 新しいエントリを保存\n", + " this.cache.set(key, {\n", + " value,\n", + " timeoutId\n", + " });\n", + " \n", + " return hadKey;\n", + " }\n", + " \n", + " /**\n", + " * キーに対応する値を取得\n", + " * @param key - 取得するキー\n", + " * @returns 未期限切れのキーが存在すれば対応する値、存在しなければ-1\n", + " * @complexity Time: O(1), Space: O(1)\n", + " */\n", + " get(key: number): number {\n", + " const entry = this.cache.get(key);\n", + " \n", + " // undefinedチェック(型安全)\n", + " if (entry === undefined) {\n", + " return -1;\n", + " }\n", + " \n", + " return entry.value;\n", + " }\n", + " \n", + " /**\n", + " * 未期限切れキーの数を取得\n", + " * @returns アクティブなキーの数\n", + " * @complexity Time: O(1), Space: O(1)\n", + " */\n", + " count(): number {\n", + " return this.cache.size;\n", + " }\n", + "}\n", + "\n", + "/**\n", + " * const timeLimitedCache = new TimeLimitedCache()\n", + " * timeLimitedCache.set(1, 42, 1000); // false\n", + " * timeLimitedCache.get(1) // 42\n", + " * timeLimitedCache.count() // 1\n", + " */\n", + "```\n", + "\n", + "## 5. TypeScript固有の最適化観点\n", + "\n", + "### 型安全性の活用\n", + "\n", + "1. **コンパイル時エラー防止**\n", + " - `Map`で型が完全に推論される\n", + " - `entry?.value`のようなオプショナルチェーンも可能だが、明示的な`undefined`チェックで意図を明確化\n", + "\n", + "2. **インターフェース定義**\n", + " - `CacheEntry`インターフェースで内部構造を型レベルで文書化\n", + " - `readonly`修飾子でイミュータビリティを表現\n", + "\n", + "3. **null/undefined安全性**\n", + " - `Map.get()`の戻り値が`CacheEntry | undefined`と正確に型付け\n", + " - strictNullChecksにより安全なチェック強制\n", + "\n", + "### 実装上の工夫\n", + "\n", + "1. **メモリリーク防止**\n", + " - `clearTimeout()`で既存タイマーを確実にクリア\n", + " - `setTimeout`のコールバックで自動削除\n", + "\n", + "2. **パフォーマンス最適化**\n", + " - Mapの特性を活かしたO(1)操作\n", + " - 不要なオブジェクト生成を回避\n", + "\n", + "3. **可読性とメンテナンス性**\n", + " - メソッド名が意図を明確に表現\n", + " - JSDocコメントで使用方法を文書化\n", + " - 計算量をコメントで明示\n", + "\n", + "# パフォーマンス改善版の実装\n", + "\n", + "現在の実装は **Runtime 54ms (30.18%)** ということで、改善の余地があります。主な最適化ポイントを分析して改善版を提示します。\n", + "\n", + "## 改善ポイントの分析\n", + "\n", + "### 現在の実装の問題点\n", + "\n", + "1. **setTimeout/clearTimeoutのオーバーヘッド**\n", + " - 各`set`呼び出しで必ずタイマー操作が発生\n", + " - 既存エントリの`clearTimeout`→新規`setTimeout`の2回の呼び出し\n", + "\n", + "2. **Map操作の回数**\n", + " - `get` → `delete` → `set`と複数回のMap操作\n", + "\n", + "3. **オブジェクト生成コスト**\n", + " - 毎回新しい`CacheEntry`オブジェクトを生成\n", + "\n", + "### 最適化戦略\n", + "\n", + "1. **タイマー処理の最適化**: 期限時刻を保存し、遅延チェック方式に変更\n", + "2. **Map操作の削減**: 単一の更新操作にまとめる\n", + "3. **オブジェクト再利用**: 構造を単純化\n", + "\n", + "## 改善版実装\n", + "\n", + "```typescript\n", + "// Analyze Complexity\n", + "// Runtime 49 ms\n", + "// Beats 55.44%\n", + "// Memory 55.56 MB\n", + "// Beats 27.72%\n", + "/**\n", + " * キャッシュエントリの内部構造(最適化版)\n", + " * - タイマーIDの代わりに期限時刻を保存\n", + " * - よりシンプルな構造で高速化\n", + " */\n", + "interface CacheEntry {\n", + " value: number;\n", + " expiresAt: number; // 期限時刻(ミリ秒)\n", + "}\n", + "\n", + "/**\n", + " * 有効期限付きキャッシュクラス(最適化版)\n", + " * @description 遅延削除方式で高速化を実現\n", + " * @complexity \n", + " * - set: Time O(1), Space O(1) - タイマー操作なし\n", + " * - get: Time O(1), Space O(1) - 期限チェックのみ\n", + " * - count: Time O(n), Space O(1) - 期限切れを除外してカウント\n", + " */\n", + "class TimeLimitedCache {\n", + " private cache: Map;\n", + " \n", + " constructor() {\n", + " this.cache = new Map();\n", + " }\n", + " \n", + " /**\n", + " * キーと値を設定し、有効期限を指定する\n", + " * @param key - キー\n", + " * @param value - 値\n", + " * @param duration - 有効期限(ミリ秒)\n", + " * @returns 既存の未期限切れキーが存在した場合true、それ以外false\n", + " * @complexity Time: O(1), Space: O(1)\n", + " */\n", + " set(key: number, value: number, duration: number): boolean {\n", + " const now = Date.now();\n", + " const expiresAt = now + duration;\n", + " \n", + " const existingEntry = this.cache.get(key);\n", + " \n", + " // 既存エントリが存在し、かつ未期限切れかチェック\n", + " const hadUnexpiredKey = existingEntry !== undefined && existingEntry.expiresAt > now;\n", + " \n", + " // 新しいエントリを設定(直接更新、タイマー不要)\n", + " this.cache.set(key, { value, expiresAt });\n", + " \n", + " return hadUnexpiredKey;\n", + " }\n", + " \n", + " /**\n", + " * キーに対応する値を取得\n", + " * @param key - 取得するキー\n", + " * @returns 未期限切れのキーが存在すれば対応する値、存在しなければ-1\n", + " * @complexity Time: O(1), Space: O(1)\n", + " */\n", + " get(key: number): number {\n", + " const entry = this.cache.get(key);\n", + " \n", + " if (entry === undefined) {\n", + " return -1;\n", + " }\n", + " \n", + " // 期限切れチェック\n", + " if (entry.expiresAt <= Date.now()) {\n", + " this.cache.delete(key); // 遅延削除\n", + " return -1;\n", + " }\n", + " \n", + " return entry.value;\n", + " }\n", + " \n", + " /**\n", + " * 未期限切れキーの数を取得\n", + " * @returns アクティブなキーの数\n", + " * @complexity Time: O(n), Space: O(1)\n", + " */\n", + " count(): number {\n", + " const now = Date.now();\n", + " let count = 0;\n", + " \n", + " // 期限切れでないエントリのみカウント\n", + " for (const entry of this.cache.values()) {\n", + " if (entry.expiresAt > now) {\n", + " count++;\n", + " }\n", + " }\n", + " \n", + " return count;\n", + " }\n", + "}\n", + "```\n", + "\n", + "## さらなる最適化版(積極的クリーンアップ付き)\n", + "\n", + "`count()`の呼び出しが多い場合はこちらの方が高速です:\n", + "\n", + "```typescript\n", + "// Analyze Complexity\n", + "// Runtime 49 ms\n", + "// Beats 55.44%\n", + "// Memory 55.31 MB\n", + "// Beats 43.33%\n", + "/**\n", + " * 有効期限付きキャッシュクラス(積極的クリーンアップ版)\n", + " */\n", + "class TimeLimitedCache {\n", + " private cache: Map;\n", + " \n", + " constructor() {\n", + " this.cache = new Map();\n", + " }\n", + " \n", + " /**\n", + " * 期限切れエントリを削除(内部ヘルパー)\n", + " * @complexity Time: O(n), Space: O(n)\n", + " */\n", + " private cleanup(): void {\n", + " const now = Date.now();\n", + " \n", + " for (const [key, entry] of this.cache) {\n", + " if (entry.expiresAt <= now) {\n", + " this.cache.delete(key);\n", + " }\n", + " }\n", + " }\n", + " \n", + " set(key: number, value: number, duration: number): boolean {\n", + " const now = Date.now();\n", + " const expiresAt = now + duration;\n", + " \n", + " const existingEntry = this.cache.get(key);\n", + " const hadUnexpiredKey = existingEntry !== undefined && existingEntry.expiresAt > now;\n", + " \n", + " this.cache.set(key, { value, expiresAt });\n", + " \n", + " return hadUnexpiredKey;\n", + " }\n", + " \n", + " get(key: number): number {\n", + " const entry = this.cache.get(key);\n", + " \n", + " if (entry === undefined) {\n", + " return -1;\n", + " }\n", + " \n", + " if (entry.expiresAt <= Date.now()) {\n", + " this.cache.delete(key);\n", + " return -1;\n", + " }\n", + " \n", + " return entry.value;\n", + " }\n", + " \n", + " count(): number {\n", + " this.cleanup(); // count時に一括クリーンアップ\n", + " return this.cache.size;\n", + " }\n", + "}\n", + "```\n", + "\n", + "## パフォーマンス比較\n", + "\n", + "| 実装方式 | set | get | count | メモリ | 備考 |\n", + "|---------|-----|-----|-------|--------|------|\n", + "| 元実装(setTimeout) | O(1) + タイマー | O(1) | O(1) | 良 | タイマーオーバーヘッド大 |\n", + "| 遅延削除版 | O(1) | O(1) | O(n) | 良 | タイマー不要で高速 |\n", + "| 積極削除版 | O(1) | O(1) | O(n) | 最良 | count時に自動クリーンアップ |\n", + "\n", + "## 推奨実装\n", + "\n", + "**遅延削除版(最初の改善版)** を推奨します。理由:\n", + "\n", + "1. **Runtime改善**: setTimeout/clearTimeoutの完全排除で30-40%高速化が期待できる\n", + "2. **シンプル**: 実装が明快で保守性が高い\n", + "3. **メモリ効率**: 既に74.74%と良好なメモリ使用量を維持\n", + "4. **実用的**: LeetCodeの制約条件(最大100アクション)では`count`のO(n)は問題なし\n", + "\n", + "この実装で **Runtime 40ms以下、上位50%以上** を狙えるはずです!" + ] + } + ], + "metadata": { + "language_info": { + "name": "typescript" + } + }, + "nbformat": 4, + "nbformat_minor": 5 } \ No newline at end of file diff --git a/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README.md b/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README.md index a3fad594..071f9464 100644 --- a/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README.md +++ b/JavaScript/2622. Cache With Time Limit/Claude Code Sonnet 4.5/README.md @@ -289,7 +289,7 @@ class TimeLimitedCache { /** * 期限切れエントリを一括削除 - * @complexity Time: O(n), Space: O(n) where n = 削除対象数(最悪時全エントリ) + * @complexity Time: O(n), Space: O(1) */ private cleanup(): void { const now = Date.now(); diff --git a/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/Memoize_TS.ipynb b/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/Memoize_TS.ipynb index 14274e49..049d1786 100644 --- a/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/Memoize_TS.ipynb +++ b/JavaScript/2623. Memoize/Claude Code Sonnet 4.5/Memoize_TS.ipynb @@ -65,9 +65,8 @@ " const memoized: Fn = function (...args: number[]): number {\n", " const key = args.join(\",\");\n", "\n", - " const cached = cache.get(key);\n", - " if (cached !== undefined) {\n", - " return cached;\n", + " if (cache.has(key)) {\n", + " return cache.get(key)!;\n", " }\n", "\n", " callCount += 1;\n", @@ -142,9 +141,8 @@ " ? args[0]\n", " : args[0] * 100001 + args[1];\n", "\n", - " const cached = cache.get(key);\n", - " if (cached !== undefined) {\n", - " return cached;\n", + " if (cache.has(key)) {\n", + " return cache.get(key)!;\n", " }\n", "\n", " callCount += 1;\n",