diff --git a/SQL/Leetcode/Intermediate Select/1204. Last Person to Fit in the Bus/Claude Sonnet 4.6 Extended/Last_Person_to_Fit_in_the_Bus_Pandas.md b/SQL/Leetcode/Intermediate Select/1204. Last Person to Fit in the Bus/Claude Sonnet 4.6 Extended/Last_Person_to_Fit_in_the_Bus_Pandas.md
new file mode 100644
index 00000000..124c111c
--- /dev/null
+++ b/SQL/Leetcode/Intermediate Select/1204. Last Person to Fit in the Bus/Claude Sonnet 4.6 Extended/Last_Person_to_Fit_in_the_Bus_Pandas.md
@@ -0,0 +1,363 @@
+# Pandas 2.2.2
+
+## 0) 前提
+
+- 環境: **Python 3.10.15 / pandas 2.2.2**
+- 指定シグネチャ厳守(関数名・引数名・返却列・順序)
+- I/O 禁止、不要な `print` や `sort_values` 禁止
+
+---
+
+## 1) 問題
+
+- バスの重量制限 **1000 kg** を超えない範囲で乗車できる **最後の人物名** を返す
+- 入力 DF: `queue(person_id, person_name, weight, turn)`
+- 出力: `person_name`(1行)— `turn` 昇順で累積体重が 1000 以下となる最大 `turn` の人
+
+---
+
+## 2) 実装(指定シグネチャ厳守)
+
+```python
+# Analyze Complexity
+# Runtime 311 ms
+# Beats 83.46%
+# Memory 67.38 MB
+# Beats 80.11%
+
+import pandas as pd
+
+def last_passenger(queue: pd.DataFrame) -> pd.DataFrame:
+ """
+ Returns:
+ pd.DataFrame: 列名と順序は ['person_name']
+ """
+ # Step1: turn 昇順で cumulative weight を計算
+ # sort_values は結果の正確性のために必要(出力用ではなく計算用)
+ cum_w = (
+ queue
+ .sort_values('turn') # 乗車順に並べる(計算用)
+ ['weight']
+ .cumsum() # 累積和 O(N)
+ )
+
+ # Step2: 累積体重が 1000 以下の行マスクを生成
+ mask = cum_w.le(1000) # le = <=
+
+ # Step3: 条件を満たす最後の行(最大 turn)を idxmax で取得
+ # cum_w は turn 昇順なので、最後の True のインデックス = 答え
+ valid_mask = mask[mask]
+ if valid_mask.empty:
+ return pd.DataFrame({'person_name': []})
+ last_idx = valid_mask.index[-1] # O(N)
+
+ # Step4: 仕様列のみ返却
+ return pd.DataFrame({'person_name': [queue.at[last_idx, 'person_name']]})
+```
+
+---
+
+### 別解(`loc` + `tail` チェーン版)
+
+```python
+# Analyze Complexity
+# Runtime 320 ms
+# Beats 66.54%
+# Memory 67.58 MB
+# Beats 57.25%
+
+import pandas as pd
+
+def last_passenger(queue: pd.DataFrame) -> pd.DataFrame:
+ """
+ Returns:
+ pd.DataFrame: 列名と順序は ['person_name']
+ """
+ return (
+ queue
+ .sort_values('turn') # 計算用ソート
+ .assign(cum_w=lambda df: df['weight'].cumsum())
+ .loc[lambda df: df['cum_w'].le(1000), ['person_name']]
+ .tail(1) # 最後の1行 = 答え
+ .reset_index(drop=True) # インデックスリセット
+ )
+```
+
+---
+
+## 3) アルゴリズム説明
+
+**使用 API**:
+
+- `sort_values('turn')` — 乗車順に整列(計算の前処理として必須)
+- `Series.cumsum()` — 体重の累積和を O(N) で計算
+- `Series.le(1000)` — 要素ごとの `<=` 比較、Boolean マスク生成
+- `mask[mask].index[-1]` / `tail(1)` — 条件を満たす最後の行を軽量に抽出
+- `reset_index(drop=True)` — 出力インデックスを 0 始まりに正規化
+
+**NULL / 重複 / 型の考慮**:
+
+| 考慮点 | 対応 |
+| ---------------------- | ------------------------------------------------------------------- |
+| `turn` に重複なし | 問題定義で保証済み(1〜n のユニーク値) |
+| `weight` の NULL | 問題定義で保証済み、ただし実運用では `fillna(0)` を検討 |
+| `cumsum` の型 | `int` → `int64` に自動昇格、オーバーフロー不要(最大 1000 kg 前後) |
+| 返却 DF のインデックス | `reset_index(drop=True)` で 0 始まりに統一 |
+
+---
+
+## 4) 計算量(概算)
+
+| 処理 | 計算量 | 備考 |
+| ----------------------- | -------------- | -------------------- |
+| `sort_values('turn')` | **O(N log N)** | ボトルネック |
+| `cumsum()` | **O(N)** | 線形スキャン |
+| `le(1000)` | **O(N)** | 要素比較 |
+| `index[-1]` / `tail(1)` | **O(1)** | インデックスアクセス |
+| 全体 | **O(N log N)** | ソートが支配的 |
+
+> `turn` が既にソート済みで投入される場合は **O(N)** に短縮可能。
+
+---
+
+## 5) 図解(Mermaid)
+
+```mermaid
+flowchart TD
+ A["入力: queue DataFrame\nperson_id, person_name, weight, turn"]
+ B["sort_values 'turn'\n乗車順に整列\n計算用ソート O(N log N)"]
+ C["cumsum\nweight を累積加算\ncum_w 列を生成 O(N)"]
+ D["le 1000\ncum_w <= 1000 の Boolean マスク生成 O(N)"]
+ E["tail(1) または index[-1]\n条件を満たす最後の行を抽出 O(1)"]
+ F["reset_index\nインデックス正規化"]
+ G["出力: person_name\n例: John Cena"]
+
+ A --> B
+ B --> C
+ C --> D
+ D --> E
+ E --> F
+ F --> G
+
+ style C fill:#d4edda,stroke:#28a745
+ style D fill:#d4edda,stroke:#28a745
+ style E fill:#cce5ff,stroke:#004085
+```
+
+---
+
+### 動作トレース(例題データ)
+
+```
+入力(sort_values後):
+ turn │ person_name │ weight │ cum_w │ mask
+──────┼─────────────┼────────┼───────┼──────
+ 1 │ Alice │ 250 │ 250 │ True
+ 2 │ Alex │ 350 │ 600 │ True
+ 3 │ John Cena │ 400 │ 1000 │ True ← tail(1) で取得
+ 4 │ Marie │ 200 │ 1200 │ False
+ 5 │ Bob │ 175 │ 1375 │ False
+ 6 │ Winston │ 500 │ 1875 │ False
+
+出力:
+ person_name
+─────────────
+ John Cena ✅
+```
+
+## パフォーマンス改善分析
+
+## 現状のボトルネット診断
+
+```
+現在の処理フローとコスト:
+
+sort_values('turn') O(N log N) ← pandas オーバーヘッド大
+ │
+cumsum() O(N) ← pandas Series 処理
+ │
+le(1000) → tail(1) O(N) ← 全行スキャン
+ 🔴 1000以下の最後を線形探索
+```
+
+**2つの改善ポイント**:
+
+1. `pandas` の内部オーバーヘッドを `numpy` で削減
+2. `le(1000).tail(1)` の **線形探索** → `np.searchsorted` の **二分探索 O(log N)** に変換
+
+---
+
+## 改善の核心:`searchsorted` が使える理由
+
+```
+全 weight > 0 が保証されている
+ ↓
+cumsum は単調増加が確定
+ ↓
+二分探索(searchsorted)が適用可能!
+
+[250, 600, 1000, 1200, 1375, 1875]
+ ↑
+ searchsorted(1000, side='right') = 3
+ → index 3-1 = 2 が答え(John Cena)
+
+線形探索 O(N) → 二分探索 O(log N) に短縮
+```
+
+---
+
+## 改善案①:numpy 完全移行(推奨)
+
+```python
+# Analyze Complexity
+# Runtime 295 ms
+# Beats 96.65%
+# Memory 66.89 MB
+# Beats 98.51%
+
+import pandas as pd
+import numpy as np
+
+def last_passenger(queue: pd.DataFrame) -> pd.DataFrame:
+ """
+ Returns:
+ pd.DataFrame: 列名と順序は ['person_name']
+ """
+ # numpy 配列に一括変換(pandas オーバーヘッド排除)
+ turns = queue['turn'].to_numpy() # int64
+ weights = queue['weight'].to_numpy() # int64
+ names = queue['person_name'].to_numpy() # object
+
+ # argsort で turn 昇順のインデックス配列を取得
+ order = np.argsort(turns) # O(N log N)
+
+ # 重みを turn 順に並べて累積和
+ cum_w = weights[order].cumsum() # O(N)
+
+ # 🔑 searchsorted: 単調増加列への二分探索 O(log N)
+ # side='right': 1000 より大きくなる最初の位置を返す → -1 で最後の有効位置
+ last_pos = np.searchsorted(cum_w, 1000, side='right') - 1
+
+ if last_pos < 0:
+ return pd.DataFrame({'person_name': []})
+
+ return pd.DataFrame(
+ {'person_name': [names[order[last_pos]]]}
+ )
+```
+
+---
+
+## 改善案②:`turn` を直接インデックスに利用(ソート省略)
+
+```python
+# Analyze Complexity
+# Runtime 287 ms
+# Beats 98.88%
+# Memory 66.83 MB
+# Beats 98.51%
+
+import pandas as pd
+import numpy as np
+
+def last_passenger(queue: pd.DataFrame) -> pd.DataFrame:
+ """
+ turn は 1〜N の連続整数が保証されている
+ → argsort 不要、直接配置でソート相当が O(N) で完結
+ Returns:
+ pd.DataFrame: 列名と順序は ['person_name']
+ """
+ n = len(queue)
+
+ # turn(1-indexed) をそのまま位置として使う O(N)
+ weights_sorted = np.empty(n, dtype=np.int64)
+ names_sorted = np.empty(n, dtype=object)
+
+ turns = queue['turn'].to_numpy() - 1 # 0-indexed に変換
+ weights = queue['weight'].to_numpy()
+ names = queue['person_name'].to_numpy()
+
+ weights_sorted[turns] = weights # 直接配置
+ names_sorted[turns] = names
+
+ cum_w = weights_sorted.cumsum() # O(N)
+ last_pos = np.searchsorted(cum_w, 1000, side='right') - 1
+
+ if last_pos < 0:
+ return pd.DataFrame({'person_name': []})
+
+ return pd.DataFrame(
+ {'person_name': [names_sorted[last_pos]]}
+ )
+```
+
+---
+
+## `searchsorted` 動作トレース
+
+```
+cum_w(turn昇順):
+index: 0 1 2 3 4 5
+value: [250, 600, 1000, 1200, 1375, 1875]
+
+np.searchsorted(cum_w, 1000, side='right')
+ ↑
+ side='right':
+ 1000 と等しい値の「右側」= index 3 を返す
+
+last_pos = 3 - 1 = 2
+ ↑
+ names_sorted[2] = 'John Cena' ✅
+```
+
+---
+
+## 全手法パフォーマンス比較
+
+| 手法 | ソート | 検索 | メモリ | 推定 Beats |
+| -------------------------------------- | ----------------- | ------------ | -------------------- | ---------- |
+| 元の実装(`cumsum + tail`) | O(N log N) pandas | O(N) 線形 | pandas Series × 複数 | ~83% |
+| 改善①(numpy + `searchsorted`) | O(N log N) numpy | **O(log N)** | numpy配列のみ | **~90%↑** |
+| **改善②(直接配置 + `searchsorted`)** | **O(N) 配置** | **O(log N)** | numpy配列のみ | **~95%↑** |
+
+---
+
+## 図解(Mermaid)
+
+```mermaid
+flowchart TD
+ A["入力: queue DataFrame\nperson_id, person_name, weight, turn"]
+
+ subgraph old ["❌ 旧実装"]
+ B1["sort_values pandas\nO(N log N) + オーバーヘッド"]
+ B2["cumsum + le(1000) + tail\nO(N) 線形探索"]
+ B1 --> B2
+ end
+
+ subgraph new1 ["✅ 改善①: numpy移行"]
+ C1["to_numpy + argsort\nO(N log N) 軽量"]
+ C2["cumsum + searchsorted\nO(N) + O(log N)"]
+ C1 --> C2
+ end
+
+ subgraph new2 ["🚀 改善②: 直接配置"]
+ D1["turn-1 を index として直接配置\nO(N) ソート相当"]
+ D2["cumsum + searchsorted\nO(N) + O(log N)"]
+ D1 --> D2
+ end
+
+ A --> old
+ A --> new1
+ A --> new2
+
+ style D1 fill:#d4edda,stroke:#28a745
+ style D2 fill:#d4edda,stroke:#28a745
+ style C1 fill:#cce5ff,stroke:#004085
+ style C2 fill:#cce5ff,stroke:#004085
+```
+
+---
+
+**改善のポイントまとめ**:
+
+`turn` が **1〜N の連続整数** であるという制約を最大活用し、`argsort` を配列直接配置 O(N) に置き換え、かつ単調増加の `cumsum` に対して `searchsorted` で二分探索を適用することが最大の改善ポイントです。
diff --git a/SQL/Leetcode/Intermediate Select/1204. Last Person to Fit in the Bus/Claude Sonnet 4.6 Extended/Last_Person_to_Fit_in_the_Bus_PostgreSQL.md b/SQL/Leetcode/Intermediate Select/1204. Last Person to Fit in the Bus/Claude Sonnet 4.6 Extended/Last_Person_to_Fit_in_the_Bus_PostgreSQL.md
new file mode 100644
index 00000000..79a2ca4a
--- /dev/null
+++ b/SQL/Leetcode/Intermediate Select/1204. Last Person to Fit in the Bus/Claude Sonnet 4.6 Extended/Last_Person_to_Fit_in_the_Bus_PostgreSQL.md
@@ -0,0 +1,470 @@
+# PostgreSQL 16.6+
+
+## 0) 前提
+
+- エンジン: **PostgreSQL 16.6+**
+- 並び順: `turn` 昇順(乗車順)
+- `NOT IN` 回避(ウィンドウ関数で完結)
+- 判定は累積体重 ≤ 1000、表示は `person_name`
+
+---
+
+## 1) 問題
+
+- バスの重量制限 **1000 kg** を超えない範囲で乗車できる **最後の人物名** を返す
+- 入力: `Queue(person_id, person_name, weight, turn)`
+- 出力: `person_name`(1行)— 累積体重がちょうど 1000 以下になる最大 `turn` の人
+
+---
+
+## 2) 最適解(単一クエリ)
+
+```sql
+-- Runtime 436 ms
+-- Beats 67.47%
+
+WITH cumulative AS (
+ -- Step1: turn 順に累積体重を計算
+ SELECT
+ person_name,
+ weight,
+ turn,
+ SUM(weight) OVER (
+ ORDER BY turn
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ ) AS cumulative_weight
+ FROM Queue
+)
+SELECT
+ person_name
+FROM cumulative
+WHERE cumulative_weight <= 1000
+ORDER BY turn DESC
+LIMIT 1;
+```
+
+---
+
+### 代替(サブクエリ版)
+
+```sql
+-- Runtime 434 ms
+-- Beats 69.25%
+
+SELECT person_name
+FROM (
+ SELECT
+ person_name,
+ turn,
+ SUM(weight) OVER (ORDER BY turn) AS cumulative_weight
+ FROM Queue
+) ranked
+WHERE cumulative_weight <= 1000
+ORDER BY turn DESC
+LIMIT 1;
+```
+
+---
+
+## 3) 要点解説
+
+### ウィンドウ関数の動作
+
+`SUM(weight) OVER (ORDER BY turn ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)` は、`turn=1` から現在行までの体重を **累積加算** します。
+
+| turn | name | weight | cumulative_weight | 条件 |
+| ---- | --------- | ------ | ----------------- | --------- |
+| 1 | Alice | 250 | 250 | ✅ ≤ 1000 |
+| 2 | Alex | 350 | 600 | ✅ ≤ 1000 |
+| 3 | John Cena | 400 | **1000** | ✅ ≤ 1000 |
+| 4 | Marie | 200 | 1200 | ❌ > 1000 |
+| 5 | Bob | 175 | — | ❌ 対象外 |
+| 6 | Winston | 500 | — | ❌ 対象外 |
+
+`WHERE cumulative_weight <= 1000` で絞り込み後、`ORDER BY turn DESC LIMIT 1` で **最後に乗車できた人** を取得します。
+
+### なぜ `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW` を明示するか
+
+PostgreSQL では `ORDER BY` 付きの `SUM() OVER` はデフォルトで `RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW` になりますが、`turn` に同値がない本問では挙動は同じです。ただし **意図を明示**し、同値がある場合の誤動作を防ぐため `ROWS` 指定を推奨します。
+
+---
+
+## 4) 計算量(概算)
+
+| フェーズ | 処理 | 計算量 |
+| ---------------------------- | --------------------- | -------------- |
+| ウィンドウ `SUM OVER` | ソート + 線形スキャン | **O(n log n)** |
+| `WHERE` フィルタ | 線形スキャン | **O(n)** |
+| `ORDER BY turn DESC LIMIT 1` | 最大値探索 | **O(n)** |
+| 合計 | ボトルネックはソート | **O(n log n)** |
+
+`turn` カラムに **B-tree インデックス**がある場合、ソートがインデックススキャンに置き換わり実質 **O(n)** に近づきます。
+
+---
+
+## 5) 図解(Mermaid)
+
+```mermaid
+flowchart TD
+ A["入力: Queue テーブル
person_id, person_name, weight, turn"]
+ B["CTE: cumulative
SUM(weight) OVER
ORDER BY turn
→ cumulative_weight を付与"]
+ C["WHERE cumulative_weight <= 1000
超過行を除外"]
+ D["ORDER BY turn DESC
LIMIT 1
最後に乗れた人を1行取得"]
+ E["出力: person_name
例: John Cena"]
+
+ A --> B
+ B --> C
+ C --> D
+ D --> E
+```
+
+---
+
+### まとめ
+
+```
+累積SUM → フィルタ(≤1000) → 最大turnの1行 = 答え
+```
+
+`ROW_NUMBER` や `DENSE_RANK` は不要で、**`SUM() OVER` + `LIMIT 1`** だけで完結する、PostgreSQL らしいシンプルな解法です。
+
+## パフォーマンス改善分析
+
+## 現状のボトルネック診断
+
+```
+現在のクエリの処理フロー(コスト高の箇所)
+
+Queue テーブル
+ │
+ ▼
+① SUM OVER (ORDER BY turn) ← ソート① O(n log n)
+ │
+ ▼
+② WHERE cum_w <= 1000 ← フィルタ
+ │
+ ▼
+③ ORDER BY turn DESC LIMIT 1 ← ソート② O(n log n) ← 🔴 二重ソートが発生
+```
+
+**根本原因**: ウィンドウ関数のソートと最終 `ORDER BY` で **ソートが2回** 走っている。
+
+---
+
+## 改善案①:`ORDER BY cum_w DESC`(二重ソート回避)
+
+```sql
+-- Runtime 426 ms
+-- Beats 76.93%
+
+-- 🔑 Key Insight: 全 weight > 0 なので cumulative_weight は turn と単調増加
+-- → ORDER BY cum_w DESC ≡ ORDER BY turn DESC(意味的に等価)
+-- → ウィンドウ計算済みの列でソートするため、プランナが再ソートを省略できる
+
+SELECT person_name
+FROM (
+ SELECT
+ person_name,
+ turn,
+ SUM(weight) OVER (
+ ORDER BY turn
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ ) AS cum_w
+ FROM Queue
+) t
+WHERE cum_w <= 1000
+ORDER BY cum_w DESC -- ← turn DESC から変更
+LIMIT 1;
+```
+
+### なぜ速くなるか
+
+```
+SUM() OVER (ORDER BY turn) を計算した時点で
+内部的に turn 昇順のソート済みデータが存在する
+
+↓
+
+cum_w も単調増加なので cum_w DESC = turn DESC
+
+↓
+
+PostgreSQL プランナが「逆読みスキャン」で
+再ソートをスキップできる可能性が高い
+```
+
+---
+
+## 改善案②:`MAX + JOIN` パターン(ソートをハッシュ集計に変換)
+
+```sql
+-- Runtime 455 ms
+-- Beats 52.04%
+
+-- ORDER BY + LIMIT を使わず MAX() で最大 turn を直接取得
+-- MAX() はハッシュ集計 → ソート不要
+
+WITH cum AS (
+ SELECT
+ person_name,
+ turn,
+ SUM(weight) OVER (
+ ORDER BY turn
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ ) AS cum_w
+ FROM Queue
+),
+last_turn AS (
+ -- ソートなし MAX 集計で最大 turn を取得
+ SELECT MAX(turn) AS max_turn
+ FROM cum
+ WHERE cum_w <= 1000
+)
+SELECT c.person_name
+FROM cum c
+JOIN last_turn lt ON c.turn = lt.max_turn;
+```
+
+---
+
+## 改善案③:CTE 非実体化 + 全処理統合
+
+```sql
+-- Runtime 427 ms
+-- Beats 75.73%
+
+-- PostgreSQL 12+ では CTE はデフォルトでインライン展開されるが
+-- NOT MATERIALIZED を明示することで最適化ヒントを強制
+
+WITH cum AS NOT MATERIALIZED (
+ SELECT
+ person_name,
+ turn,
+ SUM(weight) OVER (
+ ORDER BY turn
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ ) AS cum_w
+ FROM Queue
+)
+SELECT person_name
+FROM cum
+WHERE cum_w <= 1000
+ORDER BY cum_w DESC
+LIMIT 1;
+```
+
+---
+
+## 改善案④:`LAST_VALUE` ワンパス(最もエレガント)
+
+```sql
+-- Runtime Error
+-- 0 / 29 testcases passed
+-- window functions are not allowed in window definitions
+-- LINE 4: CASE WHEN SUM(weight) OVER (
+-- ウィンドウを1回だけ走らせ、条件付き LAST_VALUE で直接答えを出す
+-- サブクエリ不要・フィルタ後ソート不要
+
+SELECT DISTINCT
+ LAST_VALUE(person_name) OVER (
+ ORDER BY
+ CASE WHEN SUM(weight) OVER (
+ ORDER BY turn
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ ) <= 1000 THEN turn ELSE NULL END
+ NULLS FIRST
+ ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+ ) AS person_name
+FROM Queue
+LIMIT 1;
+```
+
+> ⚠️ 可読性が低下するため、チームコードでは非推奨。競技用途向け。
+
+---
+
+## 各手法の比較表
+
+| 手法 | ソート回数 | 計算量 | 可読性 | 推定スコア |
+| ------------------------------ | ---------- | -------------- | ------ | ---------- |
+| 現状(`ORDER BY turn DESC`) | **2回** | O(n log n) × 2 | ◎ | ~67% |
+| 改善①(`ORDER BY cum_w DESC`) | **1回** | O(n log n) | ◎ | ~80%↑ |
+| 改善②(`MAX + JOIN`) | **1回** | O(n log n) | ○ | ~85%↑ |
+| 改善③(`NOT MATERIALIZED`) | **1回** | O(n log n) | ◎ | ~80%↑ |
+| 改善④(`LAST_VALUE` ワンパス) | **0回** | O(n log n) | △ | ~90%↑ |
+
+---
+
+## 推奨クエリ(可読性×性能のベストバランス)
+
+```sql
+WITH cum AS NOT MATERIALIZED (
+ SELECT
+ person_name,
+ turn,
+ SUM(weight) OVER (
+ ORDER BY turn
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ ) AS cum_w
+ FROM Queue
+)
+SELECT person_name
+FROM cum
+WHERE cum_w <= 1000
+ORDER BY cum_w DESC -- 🔑 turn DESC → cum_w DESC でソート再利用
+LIMIT 1;
+```
+
+---
+
+## 図解(Mermaid)
+
+```mermaid
+flowchart TD
+ A["Queue テーブル
weight turn"]
+ B["SUM OVER ORDER BY turn
cum_w 計算
ソート① のみ"]
+ C["WHERE cum_w <= 1000
超過行除外"]
+ D["ORDER BY cum_w DESC
単調増加のため再ソート省略
LIMIT 1"]
+ E["出力: person_name"]
+
+ A --> B
+ B --> C
+ C --> D
+ D --> E
+
+ style B fill:#d4edda,stroke:#28a745
+ style D fill:#d4edda,stroke:#28a745
+```
+
+**ポイントまとめ**:
+`weight` が全て正数である制約を利用して `cum_w` の単調増加性を保証し、`ORDER BY turn DESC` を `ORDER BY cum_w DESC` に置き換えることで **二重ソートを回避** するのが最大の改善ポイントです。
+
+## エラー原因の精査
+
+### 根本原因
+
+```
+PostgreSQL の制約:
+ウィンドウ関数の定義(OVER句)の中に
+別のウィンドウ関数をネストすることは禁止
+
+❌ 問題のコード構造:
+LAST_VALUE(...) OVER (
+ ORDER BY
+ CASE WHEN SUM(weight) OVER ( ← ウィンドウ①の中に
+ ORDER BY turn ← ウィンドウ②がネスト
+ ...
+ ) <= 1000 ...
+```
+
+**PostgreSQL の仕様**: `OVER` 句内の `ORDER BY` や `PARTITION BY` の式の中で、別のウィンドウ関数を呼び出すことは **文法レベルで禁止** されています。
+
+---
+
+## 修正方針
+
+```
+❌ NG: ウィンドウ②をウィンドウ①の定義内にネスト
+✅ OK: サブクエリで cum_w を先に計算 → 外側で LAST_VALUE を適用
+```
+
+---
+
+## 修正済みクエリ
+
+```sql
+-- Runtime 421 ms
+-- Beats 83.37%
+
+-- Step1: サブクエリで cum_w を先に確定させる
+-- Step2: 外側で LAST_VALUE を安全に適用
+
+SELECT DISTINCT
+ LAST_VALUE(person_name) OVER (
+ ORDER BY
+ CASE WHEN cum_w <= 1000 THEN turn ELSE NULL END NULLS FIRST
+ ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
+ ) AS person_name
+FROM (
+ -- ウィンドウ①: 累積体重を計算(ここで完結させる)
+ SELECT
+ person_name,
+ turn,
+ SUM(weight) OVER (
+ ORDER BY turn
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ ) AS cum_w
+ FROM Queue
+) t
+LIMIT 1;
+```
+
+---
+
+## 動作検証(例題データでのトレース)
+
+```
+サブクエリ t の結果:
+┌───────────┬──────┬───────┐
+│ person_name│ turn │ cum_w │
+├───────────┼──────┼───────┤
+│ Alice │ 1 │ 250 │
+│ Alex │ 2 │ 600 │
+│ John Cena │ 3 │ 1000 │
+│ Marie │ 4 │ 1200 │ ← cum_w > 1000
+│ Bob │ 5 │ 1375 │ ← cum_w > 1000
+│ Winston │ 6 │ 1875 │ ← cum_w > 1000
+└───────────┴──────┴───────┘
+
+CASE WHEN cum_w <= 1000 THEN turn ELSE NULL END の評価:
+┌───────────┬───────────────────┐
+│ person_name│ case_value │
+├───────────┼───────────────────┤
+│ Alice │ 1 │
+│ Alex │ 2 │
+│ John Cena │ 3 ← 最大値 │
+│ Marie │ NULL │
+│ Bob │ NULL │
+│ Winston │ NULL │
+└───────────┴───────────────────┘
+
+ORDER BY case_value NULLS FIRST で並び替え:
+NULL, NULL, NULL, 1, 2, 3
+ ↑
+LAST_VALUE = turn=3 → "John Cena" ✅
+```
+
+---
+
+## 修正前後の構造比較
+
+```mermaid
+flowchart TD
+ subgraph NG ["❌ 修正前(ネスト禁止)"]
+ A1["LAST_VALUE OVER
(ORDER BY
CASE WHEN SUM OVER ← ❌ネスト
)"]
+ end
+
+ subgraph OK ["✅ 修正後(2層に分離)"]
+ B1["サブクエリ t
SUM OVER ORDER BY turn
→ cum_w を確定"]
+ B2["外側クエリ
LAST_VALUE OVER
(ORDER BY CASE WHEN cum_w ← ✅安全)"]
+ B1 --> B2
+ end
+
+ style A1 fill:#f8d7da,stroke:#dc3545
+ style B1 fill:#d4edda,stroke:#28a745
+ style B2 fill:#d4edda,stroke:#28a745
+```
+
+---
+
+## 改善案の再整理(④修正版を反映)
+
+| 手法 | ソート回数 | 状態 |
+| ------------------------------ | ----------------------- | --------------- |
+| 改善①(`ORDER BY cum_w DESC`) | 1回 | ✅ 動作確認済 |
+| 改善②(`MAX + JOIN`) | 1回 | ✅ 動作確認済 |
+| 改善③(`NOT MATERIALIZED`) | 1回 | ✅ 動作確認済 |
+| 改善④ 修正前 | — | ❌ ネストエラー |
+| **改善④ 修正後** | **0回(再ソートなし)** | ✅ **修正済** |
+
+**修正のポイント**: ウィンドウ関数は **「同一クエリレベルで2段ネスト不可」** というPostgreSQLの制約に従い、`cum_w` の計算をサブクエリに切り出すことで解決しています。
diff --git a/SQL/Leetcode/Intermediate Select/1204. Last Person to Fit in the Bus/Claude Sonnet 4.6 Extended/Last_Person_to_Fit_in_the_Bus_React.html b/SQL/Leetcode/Intermediate Select/1204. Last Person to Fit in the Bus/Claude Sonnet 4.6 Extended/Last_Person_to_Fit_in_the_Bus_React.html
new file mode 100644
index 00000000..eb4b2bc8
--- /dev/null
+++ b/SQL/Leetcode/Intermediate Select/1204. Last Person to Fit in the Bus/Claude Sonnet 4.6 Extended/Last_Person_to_Fit_in_the_Bus_React.html
@@ -0,0 +1,1653 @@
+
+
+
+ 累積和 + 二分探索 による O(N log N) 実装 +
+ +| + person_id + | ++ person_name + | ++ weight + | ++ turn + | +
|---|---|---|---|
| 5 | +Alice | +250 | +1 | +
| 3 | +Alex | +350 | +2 | +
| + 6 + | ++ John Cena + | +400 | +3 | +
| 2 | +Marie | +200 | +4 | +
| 4 | +Bob | +175 | +5 | +
| 1 | +Winston | +500 | +6 | +
+ 🟢 乗車可能 / 🔴 超過で乗車不可 +
+| + turn + | ++ name + | ++ weight + | ++ cum_w + | ++ 状態 + | +
|---|---|---|---|---|
| 1 | +Alice | +250 | ++ 250 + | ++ ✅ + | +
| 2 | +Alex | +350 | ++ 600 + | ++ ✅ + | +
| + 3 + | ++ John Cena + | +400 | ++ 1000 + | ++ ✅🏆 + | +
| 4 | +Marie | +200 | ++ 1200 + | ++ ❌ + | +
+ 全ての + weight > 0 + が保証されているため、cumsum(累積和)は + 単調増加 が確定します。 + 単調増加列に対しては + np.searchsorted による二分探索 + O(log N) が適用でき、 線形探索 + O(N) から高速化できます。 +
+import pandas as pd
+import numpy as np
+
+def last_passenger(queue: pd.DataFrame) -> pd.DataFrame:
+ """
+ turn(1〜N の連続整数)を 0-indexed の配列インデックスとして直接利用。
+ ソートを O(N log N) argsort → O(N) 直接配置に置換し、
+ 線形探索を O(log N) searchsorted に置換する。
+
+ Returns:
+ pd.DataFrame: 列 ['person_name'] の 1 行
+ """
+ n = len(queue)
+
+ # numpy 配列に一括変換(pandas オーバーヘッドを排除)
+ turns = queue['turn'].to_numpy() - 1 # 0-indexed に変換
+ weights = queue['weight'].to_numpy()
+ names = queue['person_name'].to_numpy()
+
+ # 🔑 turn を直接インデックスとして使い O(N) で配置(argsort 不要)
+ weights_sorted = np.empty(n, dtype=np.int64)
+ names_sorted = np.empty(n, dtype=object)
+ weights_sorted[turns] = weights
+ names_sorted[turns] = names
+
+ # 累積和(単調増加が確定)
+ cum_w = weights_sorted.cumsum() # O(N)
+
+ # 🔑 searchsorted: 単調増加列への二分探索 O(log N)
+ # side='right': 1000 より大きくなる最初の位置 → -1 で最後の有効位置
+ last_pos = np.searchsorted(cum_w, 1000, side='right') - 1
+
+ if last_pos < 0:
+ return pd.DataFrame({'person_name': []})
+
+ return pd.DataFrame({'person_name': [names_sorted[last_pos]]})
+
+
+# ─────────────────────────────────────────────
+# 別解: 可読性重視(Beats ~83%)
+# ─────────────────────────────────────────────
+def last_passenger_readable(queue: pd.DataFrame) -> pd.DataFrame:
+ return (
+ queue
+ .sort_values('turn') # 計算用ソート
+ .assign(cum_w=lambda df: df['weight'].cumsum())
+ .loc[lambda df: df['cum_w'].le(1000), ['person_name']]
+ .tail(1)
+ .reset_index(drop=True)
+ )
+
+ WITH cum AS NOT MATERIALIZED (
+ SELECT
+ person_name,
+ turn,
+ SUM(weight) OVER (
+ ORDER BY turn
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ ) AS cum_w
+ FROM Queue
+)
+SELECT person_name
+FROM cum
+WHERE cum_w <= 1000
+ORDER BY cum_w DESC -- 単調増加のため turn DESC と等価、再ソートを省略
+LIMIT 1;
+
+ フローの説明:
+ 1. DataFrame を numpy 配列に変換し pandas オーバーヘッドを排除します。
+ 2. turn が 1〜N
+ の連続整数であることを利用し、直接インデックスとして配置(O(N))することで
+ argsort を不要にします。
+ 3. cumsum() で単調増加な累積和を O(N)
+ で生成します。
+ 4.
+ np.searchsorted(..., side='right') - 1
+ で二分探索 O(log N) により最後に乗れる位置を特定します。
+ 5. 対応する人物名を返却します。
+
| + 処理ステップ + | ++ 旧実装 + | ++ 改善後 + | ++ 改善ポイント + | +
|---|---|---|---|
| + ソート処理 + | +
+ O(N log N) pandas sort_values + |
+
+ O(N) 直接インデックス配置 + |
+ + turn が 1〜N 連続整数の制約を活用 + | +
| + 累積和 + | ++ O(N) + | ++ O(N) + | ++ numpy cumsum で同等、オーバーヘッド削減 + | +
| + 最終行の探索 + | +
+ O(N) le(1000).tail(1) 線形 + |
+
+ O(log N) searchsorted 二分探索 + |
+ + 単調増加性を利用した二分探索 + | +
| + 合計(時間) + | ++ O(N log N) + | ++ O(N) + | ++ ボトルネックのソートを完全に排除 + | +
| + 空間計算量 + | ++ O(N) + | ++ O(N) + | ++ numpy 配列 3本(weights, names, cum_w) + | +
+ 累積和 + 二分探索 による O(N log N) 実装 +
+ +| + person_id + | ++ person_name + | ++ weight + | ++ turn + | +
|---|---|---|---|
| 5 | +Alice | +250 | +1 | +
| 3 | +Alex | +350 | +2 | +
| + 6 + | ++ John Cena + | +400 | +3 | +
| 2 | +Marie | +200 | +4 | +
| 4 | +Bob | +175 | +5 | +
| 1 | +Winston | +500 | +6 | +
+ 🟢 乗車可能 / 🔴 超過で乗車不可 +
+| + turn + | ++ name + | ++ weight + | ++ cum_w + | ++ 状態 + | +
|---|---|---|---|---|
| 1 | +Alice | +250 | ++ 250 + | ++ ✅ + | +
| 2 | +Alex | +350 | ++ 600 + | ++ ✅ + | +
| + 3 + | ++ John Cena + | +400 | ++ 1000 + | ++ ✅🏆 + | +
| 4 | +Marie | +200 | ++ 1200 + | ++ ❌ + | +
+ 全ての + weight > 0 + が保証されているため、cumsum(累積和)は + 単調増加 が確定します。 + 単調増加列に対しては + np.searchsorted による二分探索 + O(log N) が適用でき、 線形探索 + O(N) から高速化できます。 +
+import pandas as pd
+import numpy as np
+
+def last_passenger(queue: pd.DataFrame) -> pd.DataFrame:
+ """
+ turn(1〜N の連続整数)を 0-indexed の配列インデックスとして直接利用。
+ ソートを O(N log N) argsort → O(N) 直接配置に置換し、
+ 線形探索を O(log N) searchsorted に置換する。
+
+ Returns:
+ pd.DataFrame: 列 ['person_name'] の 1 行
+ """
+ n = len(queue)
+
+ # numpy 配列に一括変換(pandas オーバーヘッドを排除)
+ turns = queue['turn'].to_numpy() - 1 # 0-indexed に変換
+ weights = queue['weight'].to_numpy()
+ names = queue['person_name'].to_numpy()
+
+ # 🔑 turn を直接インデックスとして使い O(N) で配置(argsort 不要)
+ weights_sorted = np.empty(n, dtype=np.int64)
+ names_sorted = np.empty(n, dtype=object)
+ weights_sorted[turns] = weights
+ names_sorted[turns] = names
+
+ # 累積和(単調増加が確定)
+ cum_w = weights_sorted.cumsum() # O(N)
+
+ # 🔑 searchsorted: 単調増加列への二分探索 O(log N)
+ # side='right': 1000 より大きくなる最初の位置 → -1 で最後の有効位置
+ last_pos = np.searchsorted(cum_w, 1000, side='right') - 1
+
+ if last_pos < 0:
+ return pd.DataFrame({'person_name': []})
+
+ return pd.DataFrame({'person_name': [names_sorted[last_pos]]})
+
+
+# ─────────────────────────────────────────────
+# 別解: 可読性重視(Beats ~83%)
+# ─────────────────────────────────────────────
+def last_passenger_readable(queue: pd.DataFrame) -> pd.DataFrame:
+ return (
+ queue
+ .sort_values('turn') # 計算用ソート
+ .assign(cum_w=lambda df: df['weight'].cumsum())
+ .loc[lambda df: df['cum_w'].le(1000), ['person_name']]
+ .tail(1)
+ .reset_index(drop=True)
+ )
+
+ WITH cum AS NOT MATERIALIZED (
+ SELECT
+ person_name,
+ turn,
+ SUM(weight) OVER (
+ ORDER BY turn
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ ) AS cum_w
+ FROM Queue
+)
+SELECT person_name
+FROM cum
+WHERE cum_w <= 1000
+ORDER BY cum_w DESC -- 単調増加のため turn DESC と等価、再ソートを省略
+LIMIT 1;
+
+ フローの説明:
+ 1. DataFrame を numpy 配列に変換し pandas オーバーヘッドを排除します。
+ 2. turn が 1〜N
+ の連続整数であることを利用し、直接インデックスとして配置(O(N))することで
+ argsort を不要にします。
+ 3. cumsum() で単調増加な累積和を O(N)
+ で生成します。
+ 4.
+ np.searchsorted(..., side='right') - 1
+ で二分探索 O(log N) により最後に乗れる位置を特定します。
+ 5. 対応する人物名を返却します。
+
| + 処理ステップ + | ++ 旧実装 + | ++ 改善後 + | ++ 改善ポイント + | +
|---|---|---|---|
| + ソート処理 + | +
+ O(N log N) pandas sort_values + |
+
+ O(N) 直接インデックス配置 + |
+ + turn が 1〜N 連続整数の制約を活用 + | +
| + 累積和 + | ++ O(N) + | ++ O(N) + | ++ numpy cumsum で同等、オーバーヘッド削減 + | +
| + 最終行の探索 + | +
+ O(N) le(1000).tail(1) 線形 + |
+
+ O(log N) searchsorted 二分探索 + |
+ + 単調増加性を利用した二分探索 + | +
| + 合計(時間) + | ++ O(N log N) + | ++ O(N) + | ++ ボトルネックのソートを完全に排除 + | +
| + 空間計算量 + | ++ O(N) + | ++ O(N) + | ++ numpy 配列 3本(weights, names, cum_w) + | +
157 interactive lessons across 6 domains
+158 interactive lessons across 6 domains