Skip to content

Commit 6515c38

Browse files
authored
Merge pull request #211 from myoshi2891/dev-from-macmini
Dev from macmini
2 parents cada98b + 5b6262a commit 6515c38

10 files changed

Lines changed: 3700 additions & 4 deletions

File tree

.markdownlint.json

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,55 @@
11
{
22
"default": true,
3+
"MD001": false,
4+
"MD002": true,
5+
"MD003": { "style": "atx" },
6+
"MD004": { "style": "consistent" },
7+
"MD007": { "indent": 4 },
8+
"MD009": { "br_spaces": 2 },
9+
"MD012": true,
310
"MD013": {
4-
"line_length": 100,
11+
"line_length": 1000,
512
"code_blocks": false,
6-
"tables": false
13+
"tables": false,
14+
"headings": false,
15+
"strict": false
716
},
17+
"MD018": false,
18+
"MD019": false,
19+
"MD022": true,
20+
"MD024": { "siblings_only": true },
21+
"MD025": { "front_matter_title": "" },
22+
"MD026": { "punctuation": ".,;:!。,;:" },
23+
"MD029": { "style": "ordered" },
24+
"MD030": {
25+
"ul_single": 1,
26+
"ol_single": 1,
27+
"ul_multi": 1,
28+
"ol_multi": 1
29+
},
30+
"MD031": true,
31+
"MD032": true,
832
"MD033": {
9-
"allowed_elements": ["h1", "h2", "details", "summary", "p", "i", "footer", "div"]
10-
}
33+
"allowed_elements": [
34+
"h1",
35+
"h2",
36+
"p",
37+
"i",
38+
"footer",
39+
"br",
40+
"div",
41+
"sub",
42+
"sup",
43+
"kbd",
44+
"details",
45+
"summary"
46+
]
47+
},
48+
"MD034": false,
49+
"MD038": true,
50+
"MD040": false,
51+
"MD041": { "level": 1 },
52+
"MD042": false,
53+
"MD046": { "style": "fenced" },
54+
"MD048": { "style": "backtick" }
1155
}
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
# Unique Binary Search Trees - カタラン数による計算
2+
3+
<h2 id="toc">目次</h2>
4+
5+
- [概要](#overview)
6+
- [アルゴリズム要点(TL;DR)](#tldr)
7+
- [図解](#figures)
8+
- [正しさのスケッチ](#correctness)
9+
- [計算量](#complexity)
10+
- [Python 実装](#impl)
11+
- [CPython最適化ポイント](#cpython)
12+
- [エッジケースと検証観点](#edgecases)
13+
- [FAQ](#faq)
14+
15+
---
16+
17+
<h2 id="overview">概要</h2>
18+
19+
**問題**: 1からnまでの連続整数をすべて使って構成できる「構造的に異なるBST(二分探索木)」の個数を求める。
20+
21+
**要件**:
22+
23+
- 制約: `1 <= n <= 19`
24+
- 各ノードの値は1〜nの範囲でユニーク
25+
- 構造が異なればカウント(値の配置ではなく木の形状で判定)
26+
27+
**数学的背景**:
28+
29+
- この問題は **n番目のカタラン数 Cₙ** を求める問題に帰着される
30+
- カタラン数の定義:
31+
- `C₀ = 1`
32+
- `Cₙ = Σ(i=0 to n-1) Cᵢ * Cₙ₋₁₋ᵢ` (DP的定義)
33+
- `Cₙ = Cₙ₋₁ * 2(2n - 1) / (n + 1)` (漸化式)
34+
35+
---
36+
37+
<h2 id="tldr">アルゴリズム要点(TL;DR)</h2>
38+
39+
- **戦略**: カタラン数の漸化式を利用
40+
- **データ構造**: スカラー整数変数のみ(配列・再帰不要)
41+
- **時間計算量**: O(n)
42+
- **空間計算量**: O(1)
43+
- **メモリ実測**: 17MB台(LeetCode上位50〜80%)
44+
- **実行速度**: 0ms(100%)
45+
46+
**漸化式**:
47+
48+
```
49+
C₀ = 1
50+
Cₙ = Cₙ₋₁ * 2(2n - 1) / (n + 1)
51+
```
52+
53+
---
54+
55+
<h2 id="figures">図解</h2>
56+
57+
### フローチャート
58+
59+
```mermaid
60+
flowchart TD
61+
Start[Start numTrees n] --> Init[Initialize c equals 1]
62+
Init --> Loop{i from 1 to n}
63+
Loop -- i in range --> Calc[Update c equals c times 2 times 2i minus 1 div i plus 1]
64+
Calc --> Next[Increment i]
65+
Next --> Loop
66+
Loop -- Done --> Return[Return c]
67+
```
68+
69+
**説明**:
70+
71+
- `c` を1で初期化(C₀ = 1)
72+
- iを1からnまでループし、各ステップで漸化式を適用
73+
- 整数除算 `//` を使うことでPythonの任意精度整数でも正確に計算
74+
75+
### データフロー図
76+
77+
```mermaid
78+
graph LR
79+
subgraph Input_validation
80+
A[Input n] --> B[Check 1 le n le 19]
81+
end
82+
subgraph Core_computation
83+
B --> C[Initialize c equals 1]
84+
C --> D[Loop i equals 1 to n]
85+
D --> E[Apply recurrence formula]
86+
end
87+
E --> F[Output count]
88+
```
89+
90+
**説明**:
91+
92+
- 入力検証(業務版のみ)
93+
- コア計算:ループ内で漸化式を逐次適用
94+
- 最終的なカウントを出力
95+
96+
---
97+
98+
<h2 id="correctness">正しさのスケッチ</h2>
99+
100+
**不変条件**:
101+
102+
- ループのi回目終了時、変数 `c``Cᵢ`(i個のノードでのBST数)を保持
103+
104+
**基底条件**:
105+
106+
- `C₀ = 1`: 0個のノードでは空木が1通り(ループ前の初期値)
107+
108+
**帰納ステップ**:
109+
110+
- `Cᵢ` が正しいと仮定すると、漸化式 `Cᵢ₊₁ = Cᵢ * 2(2(i+1) - 1) / ((i+1) + 1)` により `Cᵢ₊₁` も正しい
111+
- Pythonの整数除算 `//` は常に正確な整数結果を返すため、誤差は発生しない
112+
113+
**終了性**:
114+
115+
- ループは `range(1, n+1)` で必ず有限回で終了
116+
117+
**網羅性**:
118+
119+
- カタラン数の定義により、すべての構造的に異なるBSTが正確にカウントされる
120+
121+
---
122+
123+
<h2 id="complexity">計算量</h2>
124+
125+
| 項目 | 計算量 | 備考 |
126+
| ---- | -------- | ----------------------------------------- |
127+
| 時間 | **O(n)** | ループがn回実行され、各ステップは定数時間 |
128+
| 空間 | **O(1)** | 変数c、i のみ使用(配列不要) |
129+
130+
**比較表(他アプローチとの対比)**:
131+
132+
| アプローチ | 時間 | 空間 | 可読性 | 備考 |
133+
| ---------------- | ----- | ---- | ------ | -------------------------- |
134+
| 漸化式(本実装) | O(n) | O(1) | ★★★ | 最もシンプルで高速 |
135+
| DP配列 | O(n²) | O(n) | ★★★ | 定義に忠実だが遅い |
136+
| 再帰+メモ化 | O(n²) | O(n) | ★★☆ | 関数呼び出しオーバーヘッド |
137+
138+
---
139+
140+
<h2 id="impl">Python 実装</h2>
141+
142+
```python
143+
from __future__ import annotations
144+
145+
from typing import Final
146+
147+
148+
class Solution:
149+
"""
150+
Unique Binary Search Trees 問題を解くクラス。
151+
152+
カタラン数の漸化式を用いてO(n)/O(1)で計算。
153+
"""
154+
155+
# 制約を定数として明示
156+
_MIN_N: Final[int] = 1
157+
_MAX_N: Final[int] = 19
158+
159+
def numTrees(self, n: int) -> int:
160+
"""
161+
LeetCode用エントリポイント(競技プログラミング向け実装)。
162+
163+
Args:
164+
n: ノード数 (1 <= n <= 19)
165+
166+
Returns:
167+
構造的に異なるBSTの個数
168+
169+
Complexity:
170+
Time: O(n)
171+
Space: O(1)
172+
"""
173+
# カタラン数の漸化式: C_0 = 1
174+
c: int = 1
175+
176+
# C_n = C_{n-1} * 2(2n - 1) / (n + 1)
177+
for i in range(1, n + 1):
178+
# 整数除算で誤差なく計算
179+
c = c * 2 * (2 * i - 1) // (i + 1)
180+
181+
return c
182+
183+
# ==== 業務開発向け実装(入力検証付き) ====
184+
185+
def numTrees_production(self, n: int) -> int:
186+
"""
187+
業務開発向けの防御的実装。
188+
189+
入力の型チェック・値チェックを行い、
190+
異常値の場合はTypeError/ValueErrorを送出。
191+
192+
Args:
193+
n: ノード数
194+
195+
Returns:
196+
構造的に異なるBSTの個数
197+
198+
Raises:
199+
TypeError: nがintでない、またはboolである場合
200+
ValueError: nが有限整数でない、あるいは許容範囲外の場合
201+
202+
Complexity:
203+
Time: O(n)
204+
Space: O(1)
205+
"""
206+
# --- 入力検証 ---
207+
if not isinstance(n, int) or isinstance(n, bool):
208+
# boolはintのサブクラスなので明示的に除外
209+
raise TypeError("n must be an integer")
210+
211+
if n < self._MIN_N or n > self._MAX_N:
212+
raise ValueError(
213+
f"n must be between {self._MIN_N} and {self._MAX_N}, got {n}"
214+
)
215+
216+
# --- メインロジック(カタラン数の漸化式) ---
217+
c: int = 1
218+
219+
for i in range(1, n + 1):
220+
# 整数演算のみで誤差は発生しない
221+
c = c * 2 * (2 * i - 1) // (i + 1)
222+
223+
return c
224+
```
225+
226+
**主要ステップ**:
227+
228+
1. **初期化**: `c = 1`(C₀の値)
229+
2. **ループ**: i = 1 から n まで漸化式を適用
230+
3. **更新**: `c = c * 2 * (2*i - 1) // (i + 1)`
231+
4. **返却**: 最終的な `c` がCₙ
232+
233+
**型注釈**:
234+
235+
- `from __future__ import annotations` で前方参照型も安全
236+
- Pylanceで型未解決警告なし
237+
238+
---
239+
240+
<h2 id="cpython">CPython最適化ポイント</h2>
241+
242+
1. **再帰回避**:
243+
- 再帰を使わずループで実装 → 関数呼び出しオーバーヘッド削減
244+
- スタックフレーム生成コストゼロ
245+
246+
2. **ローカル変数の活用**:
247+
- `c` をローカル変数に閉じ込めて更新
248+
- CPythonではローカル変数アクセスが最速(`LOAD_FAST`/`STORE_FAST`命令)
249+
250+
3. **整数演算の最適化**:
251+
- `n <= 19` のため、カタラン数C₁₉でも桁数は10桁程度
252+
- Pythonの任意精度整数でもオーバーヘッドは極小
253+
254+
4. **標準ライブラリ不使用**:
255+
- `functools.lru_cache``math.comb` などを使わない
256+
- 単純なループのみでCPythonが直接実行しやすい
257+
258+
5. **メモリアロケーション最小化**:
259+
- 配列・リスト・辞書を一切使わない
260+
- スカラー整数2つ(`c``i`)のみ
261+
262+
**実測結果**:
263+
264+
- Runtime: 0 ms(100%)
265+
- Memory: 17.61〜17.74 MB(50〜80%)
266+
267+
---
268+
269+
<h2 id="edgecases">エッジケースと検証観点</h2>
270+
271+
| ケース | 入力 | 期待出力 | 検証観点 |
272+
| -------- | ---- | ---------- | ---------------------------- |
273+
| 最小値 | n=1 | 1 | 基底条件 |
274+
| 小さい値 | n=2 | 2 | 漸化式の初期動作 |
275+
| 中間値 | n=3 | 5 | 一般的なケース |
276+
| 上限値 | n=19 | 1767263190 | 整数精度、オーバーフロー検証 |
277+
278+
**業務版での異常系**:
279+
280+
- `n = 0``ValueError`(範囲外)
281+
- `n = 20``ValueError`(範囲外)
282+
- `n = 1.5``TypeError`(非整数)
283+
- `n = True``TypeError`(bool除外)
284+
285+
**正常系の検証**:
286+
287+
- 各 n に対するカタラン数は既知の数列(OEIS A000108)と一致
288+
- 整数除算 `//` により誤差ゼロで計算される
289+
290+
---
291+
292+
<h2 id="faq">FAQ</h2>
293+
294+
### Q1: なぜDP配列を使わないのか?
295+
296+
**A**:
297+
298+
- カタラン数は漸化式で `Cₙ = f(Cₙ₋₁)` の形で表現できる
299+
- 過去の値を配列で保持する必要がなく、1つ前の値だけあればよい
300+
- メモリ O(1)、速度も O(n²) → O(n) に改善
301+
302+
### Q2: 整数除算 `//` でなぜ誤差が出ないのか?
303+
304+
**A**:
305+
306+
- カタラン数の漸化式は、数学的に常に整数結果を返すことが保証されている
307+
- `2(2n-1)` が常に `(n+1)` で割り切れる性質を持つ
308+
- Pythonの `//` は厳密な整数除算なので、浮動小数点誤差が発生しない
309+
310+
### Q3: n=19 でオーバーフローしないのか?
311+
312+
**A**:
313+
314+
- C₁₉ = 1767263190(約17億)
315+
- Pythonの `int` は任意精度なので、桁数に制限なし
316+
- C₁₉でも10桁程度なので、メモリ・速度ともに問題なし
317+
318+
### Q4: さらに高速化できるか?
319+
320+
**A**:
321+
322+
- 理論上は O(1) の「定数テーブル」版(n<=19なので20要素の配列をベタ書き)
323+
- ただし、実測ではほぼ差がない(0ms / 100% はすでに限界)
324+
- 可読性とのトレードオフを考えると、現実装が最適
325+
326+
### Q5: 業務コードと競技コードの使い分けは?
327+
328+
**A**:
329+
330+
- **業務版** (`numTrees_production`):
331+
- 入力検証、型チェック、docstring充実
332+
- 予期しない入力に対して適切な例外を送出
333+
- **競技版** (`numTrees`):
334+
- 制約が保証されている前提で検証を省略
335+
- 最小限のコードで最高速度を追求
336+
337+
LeetCodeでは競技版を使用し、実務では業務版を推奨。

0 commit comments

Comments
 (0)