Skip to content

Commit 3918c25

Browse files
committed
feat: add Pascal's Triangle (LeetCode 118) solution and update markdownlint config
1 parent 04047b0 commit 3918c25

7 files changed

Lines changed: 5408 additions & 6 deletions

File tree

.markdownlint.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
"MD042": false,
5757
"MD046": { "style": "fenced" },
5858
"MD048": { "style": "backtick" },
59+
"MD049": false,
60+
"MD050": false,
5961
"MD058": false,
6062
"MD060": false
6163
}

Algorithm/Other/leetcode/118. Pascal's Triangle/claude sonnet 4.6 adaptive/Pascal's_Triangle_Python.md

Lines changed: 370 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
> 🎯 **[algo-beginner スキル発火]**
2+
> 言語/カテゴリ: TypeScript
3+
> 適用ルールセット: 共通5ルール + TS固有5ルール
4+
> 参照ファイル: references/common.md + references/typescript.md
5+
6+
---
7+
8+
# Pascal's Triangle(パスカルの三角形)
9+
10+
---
11+
12+
## 1. 問題の分析
13+
14+
> 💡 **初学者向け補足**
15+
> この問題は一言で言うと、**「三角形の形に並んだ数値の表を、上から順番に行を積み上げて作る問題」** です。
16+
17+
パスカルの三角形とは何かを先に目で確認しましょう。
18+
19+
```
20+
行0: [1]
21+
行1: [1, 1]
22+
行2: [1, 2, 1]
23+
行3: [1, 3, 3, 1]
24+
行4: [1, 4, 6, 4, 1]
25+
```
26+
27+
**ルール**はたった2つです:
28+
29+
- 各行の **両端は必ず `1`**
30+
- 内側の要素は **真上の左と右の要素を足した値**(例:行2の `2` = 行1の `1 + 1`
31+
32+
---
33+
34+
### 競技プログラミング視点での分析
35+
36+
- 各行を前の行から `O(k)`(kはその行の要素数)で構築できます
37+
- 全体の要素数は `1 + 2 + 3 + ... + numRows = numRows*(numRows+1)/2` なので、**最低でも O(numRows²) の時間・空間**が必要
38+
- `numRows ≤ 30` という制約(=入力が最大30という上限)があるため、最悪でも `30*31/2 = 465要素` しか生成しません。計算量を特別に最小化しなくても十分高速です
39+
40+
### 業務開発視点での分析
41+
42+
- **型安全性**:戻り値 `number[][]`(数値の2次元配列)を明示し、各行が `number[]` であることをコンパイル時に保証します
43+
- **エラーハンドリング**:制約 `1 ≤ numRows ≤ 30` の範囲外入力を実行時に検出して弾きます
44+
- **イミュータブル志向**(=データを直接書き換えず、新しいデータとして作る考え方):各行を新しい配列として構築し、前の行を壊しません
45+
46+
### TypeScript特有の考慮点
47+
48+
- **型推論**(=型を書かなくてもTypeScriptが自動判断する機能):`prev.length` などから `number` 型が自動推論されます
49+
- `readonly number[]`(=読み取り専用の数値配列)を前行の型として使い、誤って前行を書き換えるバグをコンパイル時に防止します
50+
- 戻り値の `number[][]` の明示により、呼び出し元で型エラーが起きにくくなります
51+
52+
> 📖 **このセクションで登場した用語**
53+
>
54+
> - **コンパイル時**:TypeScriptのコードをJavaScriptに変換する段階。ここでエラーを検出すると、プログラムを実行する前にバグを発見できる
55+
> - **型安全性**:間違った型(例:数値の場所に文字列を渡すなど)のデータが紛れ込まないように守る仕組み
56+
> - **イミュータブル**:変更できない・変更しない状態のこと。元のデータを壊さないため、バグが起きにくい
57+
58+
---
59+
60+
## 2. アルゴリズムアプローチ比較
61+
62+
> 💡 **初学者向け補足**
63+
> 同じ問題でも解き方は複数あります。それぞれの「速さ(時間計算量(=処理にかかる手間の目安))」と「メモリの使いやすさ(空間計算量(=使うメモリ量の目安))」を比べて最適なものを選びます。
64+
65+
| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |
66+
| ----------------------------- | ---------- | ---------- | ------------ | -------- | ------ | --------------------------------------------------------- |
67+
| **A. 逐次行構築(今回選択)** | O(n²) | O(n²) |||| 前の行だけ見て次の行を作る |
68+
| B. 二項係数で直接計算 | O(n²) | O(n²) |||| C(n,k)公式を使う。大きいnで整数オーバーフローのリスクあり |
69+
| C. 再帰(メモ化なし) | O(n³) | O(n²) |||| 同じ値を何度も再計算するため遅い |
70+
71+
> 💡 **Big-O記法の読み方**(初学者向け)
72+
>
73+
> - `O(1)`:入力の大きさに関わらず、常に一定の時間・メモリ
74+
> - `O(n)`:入力が2倍になると処理も約2倍
75+
> - `O(n²)`:入力が2倍になると処理は約4倍(二重ループに多い)
76+
77+
> 📖 **このセクションで登場した用語**
78+
>
79+
> - **二項係数**:組み合わせの数を表す数学的な値。`C(n, k) = n! / (k! * (n-k)!)` で計算できるが、階乗(=1×2×3×…×n)が大きくなりすぎる問題がある
80+
> - **再帰**:関数が自分自身を呼び出して問題を解く方法。ツリー構造の問題に向くが、呼び出しが深くなるとメモリを消費する
81+
82+
---
83+
84+
## 3. 選択したアルゴリズムと理由
85+
86+
- **選択したアプローチ**: **A. 逐次行構築(イテレーティブ=繰り返し処理による方法)**
87+
- **理由**:
88+
- **方法Bを選ばなかった理由**:二項係数は数式で一見エレガントですが、`numRows=30`のとき `30!`(30の階乗)という非常に大きな数を扱う必要があり、JavaScriptのNumber型(=浮動小数点数)で精度が落ちるリスクがあります
89+
- **方法Cを選ばなかった理由**:再帰は同じ値を何度も計算し直すため、O(n³) と最も遅く、この問題には不向きです
90+
- **方法Aを選んだ理由**:前の行だけを参照して次の行を作る「積み上げ式」のため、計算の無駄がなく、コードの流れも人間の直感と一致していて読みやすいです
91+
92+
- **TypeScript特有の最適化ポイント**:
93+
- `readonly number[]` で前行の参照を保護し、「前行を誤って書き換えるバグ」をコンパイル時に防止します
94+
- 戻り値型 `number[][]` の明示により、呼び出し元でも型の恩恵を受けられます
95+
- 入力検証を関数冒頭で行い、不正な `numRows` が渡されたときに分かりやすいエラーを返します
96+
97+
> 📖 **このセクションで登場した用語**
98+
>
99+
> - **イテレーティブ**:ループ(for文など)で繰り返す処理方法。再帰と対比して使う語
100+
> - **浮動小数点数**:コンピュータが小数を近似値で表す方式。非常に大きな整数を扱うと誤差が生じることがある
101+
> - **コンパイル時エラー**:TypeScriptのコードをJavaScriptに変換する際に発見されるエラー。実行前にバグを発見できる
102+
103+
---
104+
105+
## 4. 実装コード
106+
107+
> 💡 **このコードの大まかな構造(骨格)**
108+
>
109+
> 1. 入力値 `numRows` の検証(範囲外の値を弾く)
110+
> 2. 結果を格納する空の2次元配列を用意する
111+
> 3. 1行目(`[1]`)を無条件でセットする
112+
> 4. 2行目以降は「前の行」を参照して両端を`1`、内側を隣合う値の和で埋める
113+
> 5. 完成した三角形全体を返す
114+
115+
```typescript
116+
// Runtime 1 ms
117+
// Beats 33.72%
118+
// Memory 56.02 MB
119+
// Beats 78.88%
120+
121+
/**
122+
* パスカルの三角形の最初の numRows 行を生成して返す
123+
* @param numRows - 生成する行数(1以上30以下)
124+
* @returns 各行を number[] で表した2次元配列
125+
* @throws {RangeError} numRows が制約範囲外のとき
126+
* @complexity Time: O(n²), Space: O(n²) n = numRows
127+
*/
128+
function generate(numRows: number): number[][] {
129+
// ─── 入力検証 ─────────────────────────────────────────────
130+
// 制約「1 ≤ numRows ≤ 30」を満たさない値が来たときに
131+
// 後続処理で意味不明なバグになる前に、分かりやすいエラーを投げる
132+
if (!Number.isInteger(numRows) || numRows < 1 || numRows > 30) {
133+
throw new RangeError(`numRows must be an integer between 1 and 30, got: ${numRows}`);
134+
}
135+
136+
// ─── 結果配列の準備 ───────────────────────────────────────
137+
// number[][] = 「数値の配列」を要素とする配列(2次元配列)
138+
// TypeScriptが戻り値の型をここで確定させるため、明示的に型を付ける
139+
const triangle: number[][] = [];
140+
141+
// ─── 行を1行ずつ積み上げる ────────────────────────────────
142+
for (let rowIndex = 0; rowIndex < numRows; rowIndex++) {
143+
// 各行は rowIndex+1 個の要素を持つ(0行目は1個、1行目は2個…)
144+
// Array.from で「長さだけ決まった配列」を作り、すべて 1 で初期化する
145+
// 初期値を 1 にする理由:両端は必ず 1 なので、後で端以外だけ上書きすれば済む
146+
const currentRow: number[] = Array.from({ length: rowIndex + 1 }, () => 1);
147+
148+
// 内側の要素を計算する(先頭と末尾は既に 1 なのでスキップ)
149+
// 例)rowIndex=3 のとき、更新するのは index 1 と 2 だけ
150+
for (let col = 1; col < rowIndex; col++) {
151+
// 前の行(readonly として参照)の左上 + 右上 = 現在のセルの値
152+
// readonly number[] を型として用いることで、前行を誤って
153+
// 書き換えるバグをコンパイル時に防止している(TypeScript固有の恩恵)
154+
const prevRow: readonly number[] = triangle[rowIndex - 1];
155+
currentRow[col] = prevRow[col - 1] + prevRow[col];
156+
}
157+
158+
// 完成した行を三角形に追加する
159+
triangle.push(currentRow);
160+
}
161+
162+
// 全行が揃った三角形を返す
163+
return triangle;
164+
}
165+
```
166+
167+
---
168+
169+
### 💡 コードの動作トレース(`numRows = 5` の場合)
170+
171+
入力がどのように変化していくかをステップごとに追います。
172+
173+
```
174+
入力: numRows = 5
175+
176+
─── 入力検証 ───
177+
Number.isInteger(5) → true
178+
5 >= 1 かつ 5 <= 30 → 検証通過 ✅
179+
180+
─── ループ開始 ───
181+
182+
[rowIndex = 0]
183+
currentRow の初期 = [1] (長さ1、すべて1)
184+
内側ループ: col の範囲 1 ~ -1 → 実行なし(両端だけの行)
185+
triangle = [[1]]
186+
187+
[rowIndex = 1]
188+
currentRow の初期 = [1, 1] (長さ2、すべて1)
189+
内側ループ: col の範囲 1 ~ 0 → 実行なし(1と1の2要素だけ)
190+
triangle = [[1], [1,1]]
191+
192+
[rowIndex = 2]
193+
currentRow の初期 = [1, 1, 1] (長さ3、すべて1)
194+
内側ループ: col=1 のみ
195+
prevRow = [1, 1]
196+
currentRow[1] = prevRow[0] + prevRow[1] = 1 + 1 = 2
197+
currentRow 確定 = [1, 2, 1]
198+
triangle = [[1], [1,1], [1,2,1]]
199+
200+
[rowIndex = 3]
201+
currentRow の初期 = [1, 1, 1, 1] (長さ4、すべて1)
202+
内側ループ: col=1, col=2
203+
col=1: prevRow=[1,2,1] → currentRow[1] = 1 + 2 = 3
204+
col=2: prevRow=[1,2,1] → currentRow[2] = 2 + 1 = 3
205+
currentRow 確定 = [1, 3, 3, 1]
206+
triangle = [[1],[1,1],[1,2,1],[1,3,3,1]]
207+
208+
[rowIndex = 4]
209+
currentRow の初期 = [1, 1, 1, 1, 1] (長さ5、すべて1)
210+
内側ループ: col=1, col=2, col=3
211+
col=1: prevRow=[1,3,3,1] → currentRow[1] = 1 + 3 = 4
212+
col=2: prevRow=[1,3,3,1] → currentRow[2] = 3 + 3 = 6
213+
col=3: prevRow=[1,3,3,1] → currentRow[3] = 3 + 1 = 4
214+
currentRow 確定 = [1, 4, 6, 4, 1]
215+
triangle = [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
216+
217+
─── 完成 ───
218+
出力: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] ✅
219+
```
220+
221+
> 📖 **このセクションで登場した用語**
222+
>
223+
> - **`readonly`**:変数の値を変更できないようにするTypeScript固有の修飾子。JavaScriptにはこの制限がなく、意図せぬ書き換えがバグの原因になりやすい。TypeScriptで `readonly` を付けるとコンパイル時に書き換えを防止できる
224+
> - **`RangeError`**:値の範囲が不正な場合に投げるエラーの種類。例えば「1〜30以外の数値」など、型は正しいが値が不正なときに使う(型そのものが違う場合は `TypeError` を使う)
225+
> - **`Array.from({ length: n }, () => 1)`**:長さ `n` の配列を作り、全要素を `1` で初期化するイディオム(=よく使われる定番の書き方)。`new Array(n).fill(1)` と同じ効果だが、こちらの方が型推論と相性が良い
226+
> - **Pure function(純粋関数)**:同じ入力を与えると必ず同じ出力を返し、外部の変数や状態を書き換えない関数。テストしやすく、バグが起きにくい
227+
228+
---
229+
230+
## 5. LeetCode 提出用コード
231+
232+
```typescript
233+
function generate(numRows: number): number[][] {
234+
const triangle: number[][] = [];
235+
236+
for (let rowIndex = 0; rowIndex < numRows; rowIndex++) {
237+
const currentRow: number[] = Array.from({ length: rowIndex + 1 }, () => 1);
238+
239+
for (let col = 1; col < rowIndex; col++) {
240+
const prevRow: readonly number[] = triangle[rowIndex - 1];
241+
currentRow[col] = prevRow[col - 1] + prevRow[col];
242+
}
243+
244+
triangle.push(currentRow);
245+
}
246+
247+
return triangle;
248+
}
249+
```
250+
251+
---
252+
253+
## TypeScript固有の最適化観点まとめ
254+
255+
### 型安全性の活用
256+
257+
- **`readonly number[]`**:前行の参照に `readonly` を付けることで「前行を書き換えてしまう」バグをコンパイル時に検出できます。JavaScriptには `readonly` の概念がなく、実行して初めてバグに気づく場合があります
258+
- **`number[][]` の明示**:戻り値型を明示することで、呼び出し元でも配列の各要素が `number[]` であるという情報が伝わります。型を省略すると、IDEの補完(IntelliSense)が弱くなります
259+
260+
### コンパイル時最適化
261+
262+
- **型推論の活用**`Array.from({ length: rowIndex + 1 }, () => 1)` の戻り値は `number[]` と自動推論されるため、`:number[]` を明示しなくても型チェックが機能します
263+
- **`Number.isInteger()`** を使った入力バリデーション:`typeof numRows === 'number'` だけでは小数(例:`5.5`)が通ってしまうため、より厳密な整数チェックを行っています
264+
265+
### 開発効率と保守性
266+
267+
- 各ループ変数 `rowIndex``col` に意味のある名前を付けることで、「何番目の行か」「何番目の列か」が一目で分かります
268+
- `triangle[rowIndex - 1]``prevRow` という変数に取り出すことで、コードが「前の行を参照している」という意図を明確に伝えます

0 commit comments

Comments
 (0)