Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
"MD042": false,
"MD046": { "style": "fenced" },
"MD048": { "style": "backtick" },
"MD049": false,
"MD050": false,
"MD058": false,
"MD060": false
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
> 🎯 **[algo-beginner スキル発火]**
> 言語/カテゴリ: TypeScript
> 適用ルールセット: 共通5ルール + TS固有5ルール
> 参照ファイル: references/common.md + references/typescript.md

---

# Pascal's Triangle(パスカルの三角形)

---

## 1. 問題の分析

> 💡 **初学者向け補足**
> この問題は一言で言うと、**「三角形の形に並んだ数値の表を、上から順番に行を積み上げて作る問題」** です。

パスカルの三角形とは何かを先に目で確認しましょう。

```
行0: [1]
行1: [1, 1]
行2: [1, 2, 1]
行3: [1, 3, 3, 1]
行4: [1, 4, 6, 4, 1]
```

**ルール**はたった2つです:

- 各行の **両端は必ず `1`**
- 内側の要素は **真上の左と右の要素を足した値**(例:行2の `2` = 行1の `1 + 1`)

---

### 競技プログラミング視点での分析

- 各行を前の行から `O(k)`(kはその行の要素数)で構築できます
- 全体の要素数は `1 + 2 + 3 + ... + numRows = numRows*(numRows+1)/2` なので、**最低でも O(numRows²) の時間・空間**が必要
- `numRows ≤ 30` という制約(=入力が最大30という上限)があるため、最悪でも `30*31/2 = 465要素` しか生成しません。計算量を特別に最小化しなくても十分高速です

### 業務開発視点での分析

- **型安全性**:戻り値 `number[][]`(数値の2次元配列)を明示し、各行が `number[]` であることをコンパイル時に保証します
- **エラーハンドリング**:制約 `1 ≤ numRows ≤ 30` の範囲外入力を実行時に検出して弾きます
- **イミュータブル志向**(=データを直接書き換えず、新しいデータとして作る考え方):各行を新しい配列として構築し、前の行を壊しません

### TypeScript特有の考慮点

- **型推論**(=型を書かなくてもTypeScriptが自動判断する機能):`prev.length` などから `number` 型が自動推論されます
- `readonly number[]`(=読み取り専用の数値配列)を前行の型として使い、誤って前行を書き換えるバグをコンパイル時に防止します
- 戻り値の `number[][]` の明示により、呼び出し元で型エラーが起きにくくなります

> 📖 **このセクションで登場した用語**
>
> - **コンパイル時**:TypeScriptのコードをJavaScriptに変換する段階。ここでエラーを検出すると、プログラムを実行する前にバグを発見できる
> - **型安全性**:間違った型(例:数値の場所に文字列を渡すなど)のデータが紛れ込まないように守る仕組み
> - **イミュータブル**:変更できない・変更しない状態のこと。元のデータを壊さないため、バグが起きにくい

---

## 2. アルゴリズムアプローチ比較

> 💡 **初学者向け補足**
> 同じ問題でも解き方は複数あります。それぞれの「速さ(時間計算量(=処理にかかる手間の目安))」と「メモリの使いやすさ(空間計算量(=使うメモリ量の目安))」を比べて最適なものを選びます。

| アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 |
| ----------------------------- | ---------- | ---------- | ------------ | -------- | ------ | --------------------------------------------------------- |
| **A. 逐次行構築(今回選択)** | O(n²) | O(n²) | 低 | 高 | 高 | 前の行だけ見て次の行を作る |
| B. 二項係数で直接計算 | O(n²) | O(n²) | 中 | 中 | 中 | C(n,k)公式を使う。大きいnで整数オーバーフローのリスクあり |
| C. 再帰(メモ化なし) | 最悪で指数的/再帰定義で変化 | O(n²) | 高 | 中 | 低 | 同じ値を何度も再計算するため非効率 |

> 💡 **Big-O記法の読み方**(初学者向け)
>
> - `O(1)`:入力の大きさに関わらず、常に一定の時間・メモリ
> - `O(n)`:入力が2倍になると処理も約2倍
> - `O(n²)`:入力が2倍になると処理は約4倍(二重ループに多い)

> 📖 **このセクションで登場した用語**
>
> - **二項係数**:組み合わせの数を表す数学的な値。`C(n, k) = n! / (k! * (n-k)!)` で計算できるが、階乗(=1×2×3×…×n)が大きくなりすぎる問題がある
> - **再帰**:関数が自分自身を呼び出して問題を解く方法。ツリー構造の問題に向くが、呼び出しが深くなるとメモリを消費する

---

## 3. 選択したアルゴリズムと理由

- **選択したアプローチ**: **A. 逐次行構築(イテレーティブ=繰り返し処理による方法)**
- **理由**:
- **方法Bを選ばなかった理由**:二項係数は数式で一見エレガントですが、`numRows=30`のとき `30!`(30の階乗)という非常に大きな数を扱う必要があり、JavaScriptのNumber型(=浮動小数点数)で精度が落ちるリスクがあります
- **方法Cを選ばなかった理由**:再帰は同じ値を何度も計算し直すため、最悪時指数時間となる可能性があり、この問題には不向きです
- **方法Aを選んだ理由**:前の行だけを参照して次の行を作る「積み上げ式」のため、計算の無駄がなく、コードの流れも人間の直感と一致していて読みやすいです

- **TypeScript特有の最適化ポイント**:
- `readonly number[]` で前行の参照を保護し、「前行を誤って書き換えるバグ」をコンパイル時に防止します
- 戻り値型 `number[][]` の明示により、呼び出し元でも型の恩恵を受けられます
- 入力検証を関数冒頭で行い、不正な `numRows` が渡されたときに分かりやすいエラーを返します

> 📖 **このセクションで登場した用語**
>
> - **イテレーティブ**:ループ(for文など)で繰り返す処理方法。再帰と対比して使う語
> - **浮動小数点数**:コンピュータが小数を近似値で表す方式。非常に大きな整数を扱うと誤差が生じることがある
> - **コンパイル時エラー**:TypeScriptのコードをJavaScriptに変換する際に発見されるエラー。実行前にバグを発見できる

---

## 4. 実装コード

> 💡 **このコードの大まかな構造(骨格)**
>
> 1. 入力値 `numRows` の検証(範囲外の値を弾く)
> 2. 結果を格納する空の2次元配列を用意する
> 3. 1行目(`[1]`)を無条件でセットする
> 4. 2行目以降は「前の行」を参照して両端を`1`、内側を隣合う値の和で埋める
> 5. 完成した三角形全体を返す

```typescript
// Runtime 1 ms
// Beats 33.72%
// Memory 56.02 MB
// Beats 78.88%

/**
* パスカルの三角形の最初の numRows 行を生成して返す
* @param numRows - 生成する行数(1以上30以下)
* @returns 各行を number[] で表した2次元配列
* @throws {RangeError} numRows が制約範囲外のとき
* @complexity Time: O(n²), Space: O(n²) n = numRows
*/
function generate(numRows: number): number[][] {
// ─── 入力検証 ─────────────────────────────────────────────
// 制約「1 ≤ numRows ≤ 30」を満たさない値が来たときに
// 後続処理で意味不明なバグになる前に、分かりやすいエラーを投げる
if (!Number.isInteger(numRows) || numRows < 1 || numRows > 30) {
throw new RangeError(`numRows must be an integer between 1 and 30, got: ${numRows}`);
}

// ─── 結果配列の準備 ───────────────────────────────────────
// number[][] = 「数値の配列」を要素とする配列(2次元配列)
// TypeScriptが戻り値の型をここで確定させるため、明示的に型を付ける
const triangle: number[][] = [];

// ─── 行を1行ずつ積み上げる ────────────────────────────────
for (let rowIndex = 0; rowIndex < numRows; rowIndex++) {
// 各行は rowIndex+1 個の要素を持つ(0行目は1個、1行目は2個…)
// Array.from で「長さだけ決まった配列」を作り、すべて 1 で初期化する
// 初期値を 1 にする理由:両端は必ず 1 なので、後で端以外だけ上書きすれば済む
const currentRow: number[] = Array.from({ length: rowIndex + 1 }, () => 1);

// 内側の要素を計算する(先頭と末尾は既に 1 なのでスキップ)
// 例)rowIndex=3 のとき、更新するのは index 1 と 2 だけ
for (let col = 1; col < rowIndex; col++) {
// 前の行(readonly として参照)の左上 + 右上 = 現在のセルの値
// readonly number[] を型として用いることで、前行を誤って
// 書き換えるバグをコンパイル時に防止している(TypeScript固有の恩恵)
const prevRow: readonly number[] = triangle[rowIndex - 1];
currentRow[col] = prevRow[col - 1] + prevRow[col];
}

// 完成した行を三角形に追加する
triangle.push(currentRow);
}

// 全行が揃った三角形を返す
return triangle;
}
```

---

### 💡 コードの動作トレース(`numRows = 5` の場合)

入力がどのように変化していくかをステップごとに追います。

```
入力: numRows = 5

─── 入力検証 ───
Number.isInteger(5) → true
5 >= 1 かつ 5 <= 30 → 検証通過 ✅

─── ループ開始 ───

[rowIndex = 0]
currentRow の初期 = [1] (長さ1、すべて1)
内側ループ: col の範囲 1 ~ -1 → 実行なし(両端だけの行)
triangle = [[1]]

[rowIndex = 1]
currentRow の初期 = [1, 1] (長さ2、すべて1)
内側ループ: col の範囲 1 ~ 0 → 実行なし(1と1の2要素だけ)
triangle = [[1], [1,1]]

[rowIndex = 2]
currentRow の初期 = [1, 1, 1] (長さ3、すべて1)
内側ループ: col=1 のみ
prevRow = [1, 1]
currentRow[1] = prevRow[0] + prevRow[1] = 1 + 1 = 2
currentRow 確定 = [1, 2, 1]
triangle = [[1], [1,1], [1,2,1]]

[rowIndex = 3]
currentRow の初期 = [1, 1, 1, 1] (長さ4、すべて1)
内側ループ: col=1, col=2
col=1: prevRow=[1,2,1] → currentRow[1] = 1 + 2 = 3
col=2: prevRow=[1,2,1] → currentRow[2] = 2 + 1 = 3
currentRow 確定 = [1, 3, 3, 1]
triangle = [[1],[1,1],[1,2,1],[1,3,3,1]]

[rowIndex = 4]
currentRow の初期 = [1, 1, 1, 1, 1] (長さ5、すべて1)
内側ループ: col=1, col=2, col=3
col=1: prevRow=[1,3,3,1] → currentRow[1] = 1 + 3 = 4
col=2: prevRow=[1,3,3,1] → currentRow[2] = 3 + 3 = 6
col=3: prevRow=[1,3,3,1] → currentRow[3] = 3 + 1 = 4
currentRow 確定 = [1, 4, 6, 4, 1]
triangle = [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]

─── 完成 ───
出力: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] ✅
```

> 📖 **このセクションで登場した用語**
>
> - **`readonly`**:変数の値を変更できないようにするTypeScript固有の修飾子。JavaScriptにはこの制限がなく、意図せぬ書き換えがバグの原因になりやすい。TypeScriptで `readonly` を付けるとコンパイル時に書き換えを防止できる
> - **`RangeError`**:値の範囲が不正な場合に投げるエラーの種類。例えば「1〜30以外の数値」など、型は正しいが値が不正なときに使う(型そのものが違う場合は `TypeError` を使う)
> - **`Array.from({ length: n }, () => 1)`**:長さ `n` の配列を作り、全要素を `1` で初期化するイディオム(=よく使われる定番の書き方)。`new Array(n).fill(1)` と同じ効果だが、こちらの方が型推論と相性が良い
> - **Pure function(純粋関数)**:同じ入力を与えると必ず同じ出力を返し、外部の変数や状態を書き換えない関数。テストしやすく、バグが起きにくい

---

## 5. LeetCode 提出用コード

```typescript
function generate(numRows: number): number[][] {
const triangle: number[][] = [];

for (let rowIndex = 0; rowIndex < numRows; rowIndex++) {
const currentRow: number[] = Array.from({ length: rowIndex + 1 }, () => 1);

for (let col = 1; col < rowIndex; col++) {
const prevRow: readonly number[] = triangle[rowIndex - 1];
currentRow[col] = prevRow[col - 1] + prevRow[col];
}

triangle.push(currentRow);
}

return triangle;
}
```

---

## TypeScript固有の最適化観点まとめ

### 型安全性の活用

- **`readonly number[]`**:前行の参照に `readonly` を付けることで「前行を書き換えてしまう」バグをコンパイル時に検出できます。JavaScriptには `readonly` の概念がなく、実行して初めてバグに気づく場合があります
- **`number[][]` の明示**:戻り値型を明示することで、呼び出し元でも配列の各要素が `number[]` であるという情報が伝わります。型を省略すると、IDEの補完(IntelliSense)が弱くなります

### コンパイル時最適化

- **型推論の活用**:`Array.from({ length: rowIndex + 1 }, () => 1)` の戻り値は `number[]` と自動推論されるため、`:number[]` を明示しなくても型チェックが機能します
- **`Number.isInteger()`** を使った入力バリデーション:`typeof numRows === 'number'` だけでは小数(例:`5.5`)が通ってしまうため、より厳密な整数チェックを行っています

### 開発効率と保守性

- 各ループ変数 `rowIndex`・`col` に意味のある名前を付けることで、「何番目の行か」「何番目の列か」が一目で分かります
- `triangle[rowIndex - 1]` を `prevRow` という変数に取り出すことで、コードが「前の行を参照している」という意図を明確に伝えます
Loading