diff --git a/SQL/Leetcode/Basic select/176. Second Highest Salary/gpt/SecondHighestSalary_mysql.md b/SQL/Leetcode/Intermediate Select/176. Second Highest Salary/gpt/SecondHighestSalary_mysql.md similarity index 100% rename from SQL/Leetcode/Basic select/176. Second Highest Salary/gpt/SecondHighestSalary_mysql.md rename to SQL/Leetcode/Intermediate Select/176. Second Highest Salary/gpt/SecondHighestSalary_mysql.md diff --git a/SQL/Leetcode/Basic select/176. Second Highest Salary/gpt/SecondHighestSalary_pandas.md b/SQL/Leetcode/Intermediate Select/176. Second Highest Salary/gpt/SecondHighestSalary_pandas.md similarity index 100% rename from SQL/Leetcode/Basic select/176. Second Highest Salary/gpt/SecondHighestSalary_pandas.md rename to SQL/Leetcode/Intermediate Select/176. Second Highest Salary/gpt/SecondHighestSalary_pandas.md diff --git a/SQL/Leetcode/Basic select/176. Second Highest Salary/gpt/SecondHighestSalary_postgres.md b/SQL/Leetcode/Intermediate Select/176. Second Highest Salary/gpt/SecondHighestSalary_postgres.md similarity index 100% rename from SQL/Leetcode/Basic select/176. Second Highest Salary/gpt/SecondHighestSalary_postgres.md rename to SQL/Leetcode/Intermediate Select/176. Second Highest Salary/gpt/SecondHighestSalary_postgres.md diff --git a/SQL/Leetcode/Basic select/177. Nth Highest Salary/gpt/NthHighestSalary_mysql.md b/SQL/Leetcode/Intermediate Select/177. Nth Highest Salary/gpt/NthHighestSalary_mysql.md similarity index 100% rename from SQL/Leetcode/Basic select/177. Nth Highest Salary/gpt/NthHighestSalary_mysql.md rename to SQL/Leetcode/Intermediate Select/177. Nth Highest Salary/gpt/NthHighestSalary_mysql.md diff --git a/SQL/Leetcode/Basic select/177. Nth Highest Salary/gpt/NthHighestSalary_pandas.md b/SQL/Leetcode/Intermediate Select/177. Nth Highest Salary/gpt/NthHighestSalary_pandas.md similarity index 100% rename from SQL/Leetcode/Basic select/177. Nth Highest Salary/gpt/NthHighestSalary_pandas.md rename to SQL/Leetcode/Intermediate Select/177. Nth Highest Salary/gpt/NthHighestSalary_pandas.md diff --git a/SQL/Leetcode/Basic select/177. Nth Highest Salary/gpt/NthHighestSalary_postgres.md b/SQL/Leetcode/Intermediate Select/177. Nth Highest Salary/gpt/NthHighestSalary_postgres.md similarity index 100% rename from SQL/Leetcode/Basic select/177. Nth Highest Salary/gpt/NthHighestSalary_postgres.md rename to SQL/Leetcode/Intermediate Select/177. Nth Highest Salary/gpt/NthHighestSalary_postgres.md diff --git a/SQL/Leetcode/Basic select/178. Rank Scores/gpt/Rank Scores_mysql.md b/SQL/Leetcode/Intermediate Select/178. Rank Scores/gpt/Rank Scores_mysql.md similarity index 100% rename from SQL/Leetcode/Basic select/178. Rank Scores/gpt/Rank Scores_mysql.md rename to SQL/Leetcode/Intermediate Select/178. Rank Scores/gpt/Rank Scores_mysql.md diff --git a/SQL/Leetcode/Basic select/178. Rank Scores/gpt/Rank Scores_pandas.md b/SQL/Leetcode/Intermediate Select/178. Rank Scores/gpt/Rank Scores_pandas.md similarity index 100% rename from SQL/Leetcode/Basic select/178. Rank Scores/gpt/Rank Scores_pandas.md rename to SQL/Leetcode/Intermediate Select/178. Rank Scores/gpt/Rank Scores_pandas.md diff --git a/SQL/Leetcode/Basic select/178. Rank Scores/gpt/Rank Scores_postgres.md b/SQL/Leetcode/Intermediate Select/178. Rank Scores/gpt/Rank Scores_postgres.md similarity index 100% rename from SQL/Leetcode/Basic select/178. Rank Scores/gpt/Rank Scores_postgres.md rename to SQL/Leetcode/Intermediate Select/178. Rank Scores/gpt/Rank Scores_postgres.md diff --git a/SQL/Leetcode/Basic select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_mysql.md b/SQL/Leetcode/Intermediate Select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_mysql.md similarity index 100% rename from SQL/Leetcode/Basic select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_mysql.md rename to SQL/Leetcode/Intermediate Select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_mysql.md diff --git a/SQL/Leetcode/Basic select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_pandas.md b/SQL/Leetcode/Intermediate Select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_pandas.md similarity index 100% rename from SQL/Leetcode/Basic select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_pandas.md rename to SQL/Leetcode/Intermediate Select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_pandas.md diff --git a/SQL/Leetcode/Basic select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_postgres.md b/SQL/Leetcode/Intermediate Select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_postgres.md similarity index 100% rename from SQL/Leetcode/Basic select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_postgres.md rename to SQL/Leetcode/Intermediate Select/180. Consecutive Numbers/gpt/ConsecutiveNumbers_postgres.md diff --git a/SQL/Leetcode/Basic select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_mysql.md b/SQL/Leetcode/Intermediate Select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_mysql.md similarity index 100% rename from SQL/Leetcode/Basic select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_mysql.md rename to SQL/Leetcode/Intermediate Select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_mysql.md diff --git a/SQL/Leetcode/Basic select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_pandas.md b/SQL/Leetcode/Intermediate Select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_pandas.md similarity index 100% rename from SQL/Leetcode/Basic select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_pandas.md rename to SQL/Leetcode/Intermediate Select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_pandas.md diff --git a/SQL/Leetcode/Basic select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_postgre.md b/SQL/Leetcode/Intermediate Select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_postgre.md similarity index 100% rename from SQL/Leetcode/Basic select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_postgre.md rename to SQL/Leetcode/Intermediate Select/550. Game Play Analysis IV/gpt/GamePlayAnalysis_IV_postgre.md diff --git a/SQL/Leetcode/Basic select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_mysql.md b/SQL/Leetcode/Intermediate Select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_mysql.md similarity index 100% rename from SQL/Leetcode/Basic select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_mysql.md rename to SQL/Leetcode/Intermediate Select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_mysql.md diff --git a/SQL/Leetcode/Basic select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_pandas.md b/SQL/Leetcode/Intermediate Select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_pandas.md similarity index 100% rename from SQL/Leetcode/Basic select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_pandas.md rename to SQL/Leetcode/Intermediate Select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_pandas.md diff --git a/SQL/Leetcode/Basic select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_posgre.md b/SQL/Leetcode/Intermediate Select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_posgre.md similarity index 100% rename from SQL/Leetcode/Basic select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_posgre.md rename to SQL/Leetcode/Intermediate Select/570. Managers with at Least 5 Direct Reports/gpt/Managers_with_at_Least_5_Direct_Reports_posgre.md diff --git a/SQL/Leetcode/Basic select/585. Investments in 2016/gpt/Investments_in_2016_mysql.ipynb b/SQL/Leetcode/Intermediate Select/585. Investments in 2016/gpt/Investments_in_2016_mysql.ipynb similarity index 100% rename from SQL/Leetcode/Basic select/585. Investments in 2016/gpt/Investments_in_2016_mysql.ipynb rename to SQL/Leetcode/Intermediate Select/585. Investments in 2016/gpt/Investments_in_2016_mysql.ipynb diff --git a/SQL/Leetcode/Basic select/585. Investments in 2016/gpt/Investments_in_2016_pandas.ipynb b/SQL/Leetcode/Intermediate Select/585. Investments in 2016/gpt/Investments_in_2016_pandas.ipynb similarity index 100% rename from SQL/Leetcode/Basic select/585. Investments in 2016/gpt/Investments_in_2016_pandas.ipynb rename to SQL/Leetcode/Intermediate Select/585. Investments in 2016/gpt/Investments_in_2016_pandas.ipynb diff --git a/SQL/Leetcode/Basic select/585. Investments in 2016/gpt/Investments_in_2016_posgre.ipynb b/SQL/Leetcode/Intermediate Select/585. Investments in 2016/gpt/Investments_in_2016_posgre.ipynb similarity index 100% rename from SQL/Leetcode/Basic select/585. Investments in 2016/gpt/Investments_in_2016_posgre.ipynb rename to SQL/Leetcode/Intermediate Select/585. Investments in 2016/gpt/Investments_in_2016_posgre.ipynb diff --git a/SQL/Leetcode/Basic select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_mysql.ipynb b/SQL/Leetcode/Intermediate Select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_mysql.ipynb similarity index 100% rename from SQL/Leetcode/Basic select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_mysql.ipynb rename to SQL/Leetcode/Intermediate Select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_mysql.ipynb diff --git a/SQL/Leetcode/Basic select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_pandas.ipynb b/SQL/Leetcode/Intermediate Select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_pandas.ipynb similarity index 100% rename from SQL/Leetcode/Basic select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_pandas.ipynb rename to SQL/Leetcode/Intermediate Select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_pandas.ipynb diff --git a/SQL/Leetcode/Basic select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_posgre.ipynb b/SQL/Leetcode/Intermediate Select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_posgre.ipynb similarity index 100% rename from SQL/Leetcode/Basic select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_posgre.ipynb rename to SQL/Leetcode/Intermediate Select/602. Friend Requests II: Who Has the Most Friends/gpt/Friend_Requests_II_Who_Has_the_Most_Friends_posgre.ipynb diff --git a/SQL/Leetcode/Basic select/608. Tree Node/gpt/Tree_Node_mysql.ipynb b/SQL/Leetcode/Intermediate Select/608. Tree Node/gpt/Tree_Node_mysql.ipynb similarity index 100% rename from SQL/Leetcode/Basic select/608. Tree Node/gpt/Tree_Node_mysql.ipynb rename to SQL/Leetcode/Intermediate Select/608. Tree Node/gpt/Tree_Node_mysql.ipynb diff --git a/SQL/Leetcode/Basic select/608. Tree Node/gpt/Tree_Node_pandas.ipynb b/SQL/Leetcode/Intermediate Select/608. Tree Node/gpt/Tree_Node_pandas.ipynb similarity index 100% rename from SQL/Leetcode/Basic select/608. Tree Node/gpt/Tree_Node_pandas.ipynb rename to SQL/Leetcode/Intermediate Select/608. Tree Node/gpt/Tree_Node_pandas.ipynb diff --git a/SQL/Leetcode/Basic select/608. Tree Node/gpt/Tree_Node_posgre.ipynb b/SQL/Leetcode/Intermediate Select/608. Tree Node/gpt/Tree_Node_posgre.ipynb similarity index 100% rename from SQL/Leetcode/Basic select/608. Tree Node/gpt/Tree_Node_posgre.ipynb rename to SQL/Leetcode/Intermediate Select/608. Tree Node/gpt/Tree_Node_posgre.ipynb diff --git a/SQL/Leetcode/Intermediate Select/626. Exchange Seats/gpt/Exchange_Seats_mysql.ipynb b/SQL/Leetcode/Intermediate Select/626. Exchange Seats/gpt/Exchange_Seats_mysql.ipynb new file mode 100644 index 00000000..be287cf3 --- /dev/null +++ b/SQL/Leetcode/Intermediate Select/626. Exchange Seats/gpt/Exchange_Seats_mysql.ipynb @@ -0,0 +1,216 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7d52f65e", + "metadata": {}, + "source": [ + "# MySQL 8.0.40\n", + "\n", + "## 0) 前提\n", + "\n", + "* エンジン: **MySQL 8**\n", + "* 並び順: 任意(`ORDER BY` を付けない)\n", + " ※この問題は仕様として **`id` 昇順で返す** ことが明記されているため、最終 `ORDER BY id` を付与します\n", + "* `NOT IN` は NULL 罠のため回避\n", + "* 判定は **ID 基準**、表示は仕様どおりの列名と順序\n", + "\n", + "## 1) 問題\n", + "\n", + "* `連番 ID を持つ Seat テーブルで、学生を 2 人ずつで席替え(ID をスワップ)する。人数が奇数なら最後の 1 人はそのまま。結果は id 昇順で返す。`\n", + "* 入力テーブル例: `Seat(id INT PK, student VARCHAR)`\n", + "* 出力仕様: `id, student`\n", + "\n", + " * 奇数 `id` は「次が存在するときだけ」`id+1` にスワップ\n", + " * 偶数 `id` は常に `id-1` にスワップ\n", + " * 最後の孤立した奇数 `id` は据え置き\n", + " * **最終出力は `id` 昇順**\n", + "\n", + "## 2) 最適解(単一クエリ)\n", + "\n", + "> ウィンドウで末尾 ID を取り、`CASE` で ±1 変換。**外側で `ORDER BY id`** を必ず付ける。\n", + "\n", + "```sql\n", + "WITH win AS (\n", + " SELECT id, student, MAX(id) OVER () AS max_id\n", + " FROM Seat\n", + ")\n", + "SELECT\n", + " CASE\n", + " WHEN id % 2 = 1 AND id < max_id THEN id + 1 -- 奇数かつ次がある\n", + " WHEN id % 2 = 0 THEN id - 1 -- 偶数は前と交換\n", + " ELSE id -- 最後の孤立奇数\n", + " END AS id,\n", + " student\n", + "FROM win\n", + "ORDER BY id; -- 仕様:id 昇順\n", + "\n", + "Runtime 316 ms\n", + "Beats 70.97%\n", + "\n", + "```\n", + "\n", + "## 3) 代替解\n", + "\n", + "> ウィンドウを避ける場合は、定数サブクエリで最大 ID を 1 回だけ取得(MySQL は定数サブクエリをキャッシュ)。性能と可読性のバランスが良い。\n", + "\n", + "```sql\n", + "SELECT\n", + " CASE\n", + " WHEN s.id % 2 = 1 AND s.id < (SELECT MAX(id) FROM Seat) THEN s.id + 1\n", + " WHEN s.id % 2 = 0 THEN s.id - 1\n", + " ELSE s.id\n", + " END AS id,\n", + " s.student\n", + "FROM Seat AS s\n", + "ORDER BY id; -- 仕様:id 昇順\n", + "\n", + "Runtime 321 ms\n", + "Beats 64.40%\n", + "\n", + "```\n", + "\n", + "## 4) 要点解説\n", + "\n", + "* **誤答の原因**は多くの場合、**`ORDER BY id` を付けていない**こと(計算後の `id` で並べ替えないと期待順にならない)\n", + "* 連番前提なのでスワップは **±1** で表現可能。末尾の奇数だけは「次がない」ため据え置き(`id < max_id` 判定)\n", + "* 余計な自己結合は不要。存在判定は `MAX(id)` を使うのがシンプル\n", + "\n", + "## 5) 計算量(概算)\n", + "\n", + "* ウィンドウ版: スキャン+ウィンドウ伝播で **O(N)**(実装上は並べ替え不要な全体関数なので追加コスト小)\n", + "* 代替版: スキャン **O(N)**、サブクエリ `MAX(id)` は 1 回評価\n", + "\n", + "## 6) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[Seat 入力] --> B[\"末尾ID MAX(id) を取得\"]\n", + " B --> C[\"CASE で ±1 変換\"]\n", + " C --> D[id 昇順で整列]\n", + " D --> E[出力 id, student]\n", + "```\n", + "\n", + "# MySQL 8.0.40\n", + "\n", + "## 0) 前提\n", + "\n", + "* エンジン: **MySQL 8**\n", + "* 並び順: 任意だが、本問は**`id` 昇順で返す指定**があるため `ORDER BY id` を付ける\n", + "* `NOT IN` は NULL 罠のため回避\n", + "* 判定は **ID 基準**、表示は仕様どおりの列名と順序\n", + "\n", + "## 1) 問題\n", + "\n", + "* `連番 ID を持つ Seat テーブルで、学生を 2 人ずつ席替え(ID スワップ)する。人数が奇数なら最後の 1 人はそのまま。結果は id 昇順で返す。`\n", + "* 入力テーブル例: `Seat(id INT PK, student VARCHAR)`\n", + "* 出力仕様: `id, student`(奇数 ↔ 直後の偶数で入れ替え。末尾の孤立奇数は据え置き)\n", + "\n", + "## 2) 最適解(単一クエリ)\n", + "\n", + "> 実行時間を詰める観点で、**ウィンドウ関数や CTE を使わず**、`MAX(id)` を 1 回だけ求めて全行に結合する「定数サブクエリ JOIN」+**ビット演算**(`& 1`)を使います。\n", + "> (`id % 2` より `id & 1` のほうが軽量。CTE は場合によりマテリアライズされオーバーヘッドになり得ます)\n", + "\n", + "```sql\n", + "SELECT\n", + " CASE\n", + " WHEN (s.id & 1) = 1 AND s.id < m.max_id THEN s.id + 1 -- 奇数かつ次がある\n", + " WHEN (s.id & 1) = 0 THEN s.id - 1 -- 偶数は前と交換\n", + " ELSE s.id -- 最後の孤立奇数\n", + " END AS id,\n", + " s.student\n", + "FROM Seat AS s\n", + "JOIN (SELECT MAX(id) AS max_id FROM Seat) AS m\n", + "ORDER BY id; -- 指定どおり id 昇順\n", + "\n", + "Runtime 345 ms\n", + "Beats 35.97%\n", + "\n", + "```\n", + "\n", + "* ポイント\n", + "\n", + " * `JOIN (SELECT MAX(id) ...)` は最適化され 1 回だけ評価されるため、**スカラーサブクエリを各行で評価**する形より安定しやすいです。\n", + " * `& 1` による偶奇判定は `MOD(id,2)` / `id % 2` より安価です。\n", + "\n", + "## 3) 代替解\n", + "\n", + "> 「分岐評価自体を減らす」アプローチ。対象行を 3 つに分解して `UNION ALL`(それぞれに最小限の述語だけを評価)。環境次第で速くなることがあります。\n", + "\n", + "```sql\n", + "SELECT s.id + 1 AS id, s.student -- 奇数で次がある\n", + "FROM Seat AS s\n", + "JOIN (SELECT MAX(id) AS max_id FROM Seat) AS m\n", + "WHERE (s.id & 1) = 1 AND s.id < m.max_id\n", + "\n", + "UNION ALL\n", + "SELECT s.id - 1, s.student -- 偶数\n", + "FROM Seat AS s\n", + "WHERE (s.id & 1) = 0\n", + "\n", + "UNION ALL\n", + "SELECT s.id, s.student -- 最後の孤立奇数\n", + "FROM Seat AS s\n", + "JOIN (SELECT MAX(id) AS max_id FROM Seat) AS m\n", + "WHERE (s.id & 1) = 1 AND s.id = m.max_id\n", + "\n", + "ORDER BY id; -- 指定どおり id 昇順\n", + "\n", + "Runtime 320 ms\n", + "Beats 65.78%\n", + "\n", + "```\n", + "\n", + "## 4) 要点解説\n", + "\n", + "* **高速化の勘所**\n", + "\n", + " * CTE+ウィンドウは可読性は高い一方で、**CTE のマテリアライズ**や**ウィンドウのフレーム管理**でコスト増になることがあります。\n", + " → 本問は「全体で `MAX(id)` を 1 回」分かればよいので、**定数サブクエリ JOIN**がシンプルかつ安定。\n", + " * **ビット演算**で偶奇判定(`id & 1`)にすると、`%` より演算コストが低め。\n", + " * `ORDER BY id` は必須(仕様)。`ORDER BY` は出力段で 1 回だけ走る構成にする。\n", + "\n", + "* **可読性 vs. 性能**\n", + "\n", + " * 可読性重視なら、これまでの CTE+`MAX(id) OVER()` も正解です。\n", + " * ランタイムが拮抗しているなら、**`JOIN (SELECT MAX(id))` + `& 1`** をまず試すのが無難。\n", + "\n", + "## 5) 計算量(概算)\n", + "\n", + "* いずれもフルスキャン主体で **O(N)**。\n", + "\n", + " * `JOIN (SELECT MAX(id))` の内側は 1 行だけの派生表で評価回数は 1 回。\n", + " * `UNION ALL` 案は 3 本のスキャンだが述語が軽く、条件分岐が少ないため実測で伸びる場合あり。\n", + "\n", + "## 6) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[Seat 入力] --> B[\"定数サブクエリで MAX(id)\"]\n", + " B --> C[\"CASE と &1 で ±1 変換\"]\n", + " C --> D[id 昇順で整列]\n", + " D --> E[出力 id, student]\n", + "```\n", + "\n", + "---\n", + "\n", + "### 小さな改善チェックリスト\n", + "\n", + "* [ ] `CASE` の偶奇判定を `id & 1` に置換\n", + "* [ ] `MAX(id)` は **CROSS/JOIN で 1 回だけ取得**\n", + "* [ ] 実行計画を確認(`EXPLAIN`)し、CTE がマテリアライズされていないかを見る\n", + "* [ ] データ量が大きい場合は `ORDER BY id` のためのソートコストが支配的。`innodb_buffer_pool_size` が十分か確認(競技環境では不可だが実務では重要)\n", + "\n", + "このあたりを適用して、あなたの 316ms / 321ms をもう一段押し下げられる可能性があります。\n", + "\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/SQL/Leetcode/Intermediate Select/626. Exchange Seats/gpt/Exchange_Seats_pandas.ipynb b/SQL/Leetcode/Intermediate Select/626. Exchange Seats/gpt/Exchange_Seats_pandas.ipynb new file mode 100644 index 00000000..96530985 --- /dev/null +++ b/SQL/Leetcode/Intermediate Select/626. Exchange Seats/gpt/Exchange_Seats_pandas.ipynb @@ -0,0 +1,210 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c3830f7f", + "metadata": {}, + "source": [ + "# Pandas 2.2.2用\n", + "\n", + "## 0) 前提\n", + "\n", + "* 環境: **Python 3.10.15 / pandas 2.2.2**\n", + "* **指定シグネチャ厳守**(関数名・引数名・返却列・順序)\n", + "* I/O 禁止、不要な `print` や `sort_values` 禁止(連番前提を活かして**並べ替え無し**で所望順に並べます)\n", + "\n", + "## 1) 問題\n", + "\n", + "* `連番 id(1..N)を持つ Seat で、2人ごとに席(id)を入れ替える。人数が奇数なら最後の1人は据え置き。結果は id 昇順。`\n", + "* 入力 DF: `Seat(id: int, student: str)`\n", + "* 出力: `id, student`(id 昇順。奇数↔直後の偶数で入替/末尾の孤立奇数はそのまま)\n", + "\n", + "## 2) 実装(指定シグネチャ厳守)\n", + "\n", + "> 0始まりに直して **LSB を XOR** でトグル(`((id-1) ^ 1) + 1`)→「末尾が奇数」の1件だけ据え置き。\n", + "> 並べ替え禁止のため、**連番 RangeIndex** を用いて `reindex` で 1..N の順を直接作ります。\n", + "\n", + "```python\n", + "import pandas as pd\n", + "\n", + "def swap_seats(seat: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " Returns:\n", + " pd.DataFrame: 列名と順序は ['id', 'student']\n", + " \"\"\"\n", + " if seat.empty:\n", + " return seat[['id', 'student']]\n", + "\n", + " s = seat[['id', 'student']].copy()\n", + " max_id = int(s['id'].max())\n", + "\n", + " # 1↔2, 3↔4, ... を一発で表す(XOR)\n", + " new_id = ((s['id'] - 1) ^ 1) + 1\n", + "\n", + " # 末尾が奇数なら、その1件だけ据え置き(id = max_id の行)\n", + " if (max_id & 1) == 1:\n", + " new_id = new_id.where(s['id'] != max_id, max_id)\n", + "\n", + " # 並べ替え無しで id 昇順を実現:連番インデックスに再配置\n", + " out_ids = pd.RangeIndex(1, max_id + 1)\n", + " # new_id をインデックスにして student を並べ替えず配置\n", + " placed = pd.Series(s['student'].to_numpy(), index=new_id.to_numpy())\n", + " out = pd.DataFrame({'id': out_ids, 'student': placed.reindex(out_ids).to_numpy()})\n", + "\n", + " return out\n", + "\n", + "Analyze Complexity\n", + "Runtime 274 ms\n", + "Beats 67.29%\n", + "Memory 66.96 MB\n", + "Beats 56.16%\n", + "\n", + "```\n", + "\n", + "## 3) アルゴリズム説明\n", + "\n", + "* 使用API/手法\n", + "\n", + " * ビット演算(XOR)でペア入替:`((id-1) ^ 1) + 1`\n", + " * `Series.where` で末尾奇数 1 件だけ据え置き\n", + " * `RangeIndex` と `reindex` で **ソート無し**に `id` 昇順を構成(連番前提を活用)\n", + "* **NULL / 重複 / 型**\n", + "\n", + " * 入力は PK 連番なので重複や欠損は想定せず、`new_id` も一意\n", + " * `id` は整数前提(演算前に `max_id` を `int` 化)\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "* 全処理はベクトル演算主体で **O(N)**、追加メモリは `new_id` と `placed` の **O(N)**\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[Seat 入力] --> B[0始まりに変換して XOR]\n", + " B --> C[末尾の孤立奇数を据え置き]\n", + " C --> D[\"RangeIndex(1..N) を用意\"]\n", + " D --> E[\"reindex で 1..N の順に配置\"]\n", + " E --> F[出力 id, student]\n", + "```\n", + "\n", + "いい数値です!さらに数 ms・数 MB を詰めるなら、**Pandas のオブジェクト生成を最小化して NumPy で配列直書き**に振るのが有効です。`Series.where` や `reindex` をやめ、**1 回のインデックス代入**で完成させます。\n", + "\n", + "---\n", + "\n", + "# Pandas 2.2.2用\n", + "\n", + "## 0) 前提\n", + "\n", + "* 環境: **Python 3.10.15 / pandas 2.2.2**\n", + "* **指定シグネチャ厳守**\n", + "* I/O 禁止、不要な `print` や `sort_values` 禁止\n", + "\n", + "## 1) 問題\n", + "\n", + "* `連番 id(1..N)を持つ Seat で 2人ごとに id を入替。人数が奇数なら最後の1人は据え置き。結果は id 昇順。`\n", + "* 入力 DF: `Seat(id: int, student: str)`\n", + "* 出力: `id, student`(id 昇順)\n", + "\n", + "## 2) 実装(指定シグネチャ厳守)\n", + "\n", + "> **完全ベクトル化 & 追加オブジェクト最小化**\n", + "> `((id-1) ^ 1) + 1` で 1↔2 / 3↔4 … を一括変換し、**最後が奇数のときだけ**ガード。\n", + "> 出力は **NumPy 配列に直接配置**してから DataFrame を 1 度だけ作成します。\n", + "\n", + "```python\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "def swap_seats(seat: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " Returns:\n", + " pd.DataFrame: 列名と順序は ['id', 'student']\n", + " \"\"\"\n", + " if seat.empty:\n", + " return seat[['id', 'student']]\n", + "\n", + " # 参照を NumPy に落として中間 Series/DataFrame を作らない\n", + " ids = seat['id'].to_numpy()\n", + " students = seat['student'].to_numpy()\n", + "\n", + " max_id = int(ids.max())\n", + " n = max_id # 連番 1..N 前提\n", + "\n", + " # 1↔2, 3↔4, ... を一括変換\n", + " new_ids = ((ids - 1) ^ 1) + 1\n", + "\n", + " # 末尾が奇数なら、最後の 1 件だけ据え置き\n", + " if (max_id & 1) == 1:\n", + " # True は最後の行 1 件のみ(連番前提)\n", + " keep_mask = (ids == max_id)\n", + " # np.where は新配列を返すが、分岐コストを最小化\n", + " new_ids = np.where(keep_mask, ids, new_ids)\n", + "\n", + " # 出力 student を 1 回の代入で配置(0-based に直す)\n", + " out_students = np.empty(n, dtype=students.dtype)\n", + " out_students[new_ids - 1] = students\n", + "\n", + " # id は 1..N をそのまま並べる(並べ替え不要)\n", + " out = pd.DataFrame({\n", + " 'id': np.arange(1, n + 1, dtype=ids.dtype),\n", + " 'student': out_students\n", + " })\n", + " return out\n", + "\n", + "Analyze Complexity\n", + "Runtime 255 ms\n", + "Beats 92.64%\n", + "Memory 66.56 MB\n", + "Beats 82.53%\n", + "\n", + "```\n", + "\n", + "### ここが効きます\n", + "\n", + "* `reindex` や `Series` 化をやめ、**NumPy への 1 回代入**に集約(メモリと CPU を削減)\n", + "* 偶奇判定は **ビット演算**(`& 1` / XOR)で高速化\n", + "* 中間コピー(`copy()`/`drop`/`where` の連鎖)を排除\n", + "\n", + "## 3) アルゴリズム説明\n", + "\n", + "* 使用 API/手法: `numpy` 演算(XOR、`where`、インデクシング)、`DataFrame` 最終 1 回生成\n", + "* **NULL / 重複 / 型**\n", + "\n", + " * 問題前提が PK 連番のため重複・欠損は非想定(`new_ids` も一意)\n", + " * `students` は object/string。型変換は不要\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "* すべて **O(N)**(1 回の代入で完成)、追加メモリは `new_ids` と `out_students` の **O(N)**\n", + " ※ 既存案より **中間オブジェクトが減る**ため、実計測で **数 %〜十数 %** の改善余地\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[Seat 入力] --> B[NumPy 配列へ取り出し]\n", + " B --> C[\"XOR で 1↔2, 3↔4\"]\n", + " C --> D[最後の孤立奇数のみ据え置き]\n", + " D --> E[出力配列に一括配置]\n", + " E --> F[DataFrame を1回だけ生成]\n", + "```\n", + "\n", + "---\n", + "\n", + "### さらに詰めたい場合(任意)\n", + "\n", + "* `students` が大きな文字列の場合、`Categorical` 化してから戻すと一部ケースでメモリが下がることがあります(ただし変換コストとトレードオフ)。\n", + "* 競技環境で許されるなら、関数外で `np.arange(1, n+1)` の再利用(キャッシュ)も微改善に。\n", + "\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/SQL/Leetcode/Intermediate Select/626. Exchange Seats/gpt/Exchange_Seats_posgres.ipynb b/SQL/Leetcode/Intermediate Select/626. Exchange Seats/gpt/Exchange_Seats_posgres.ipynb new file mode 100644 index 00000000..f01fc0d1 --- /dev/null +++ b/SQL/Leetcode/Intermediate Select/626. Exchange Seats/gpt/Exchange_Seats_posgres.ipynb @@ -0,0 +1,186 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5d3dac29", + "metadata": {}, + "source": [ + "# PostgreSQL 16.6+\n", + "\n", + "## 0) 前提\n", + "\n", + "* エンジン: **PostgreSQL 16.6+**\n", + "* 並び順: 任意\n", + " ※この問題は仕様として **`id` 昇順で返す** ため、最終的に `ORDER BY id` を付けます\n", + "* `NOT IN` 回避(`EXISTS` / `LEFT JOIN ... IS NULL` を推奨)\n", + "* 判定は **ID 基準**、表示は仕様どおり\n", + "\n", + "## 1) 問題\n", + "\n", + "* `連番 ID を持つ Seat テーブルで、学生の席(id)を 2 人ずつ入れ替える。人数が奇数なら最後の 1 人はそのまま。結果は id 昇順で返す。`\n", + "* 入力: `Seat(id INT PRIMARY KEY, student TEXT)`\n", + "* 出力: `id, student`\n", + "\n", + " * 奇数 id は「次が存在するときだけ」`id+1` にスワップ\n", + " * 偶数 id は常に `id-1` にスワップ\n", + " * 最後の孤立した奇数 id は据え置き\n", + " * **最終出力は id 昇順**\n", + "\n", + "## 2) 最適解(単一クエリ)\n", + "\n", + "> PostgreSQL では **CTE + ウィンドウ**で全体最大 `id` を取り、**ビット演算**で LSB をトグルするのが簡潔かつ高速です。\n", + "> 0 始まりに直して LSB を XOR で切り替えることで `1↔2, 3↔4, ...` の対応を **`((id-1) # 1) + 1`** の 1 式で表現できます(`#` は XOR)。末尾が奇数の 1 件だけ据え置きます。\n", + "\n", + "```sql\n", + "WITH win AS (\n", + " SELECT id, student, MAX(id) OVER () AS max_id\n", + " FROM seat\n", + ")\n", + "SELECT\n", + " CASE\n", + " WHEN (id & 1) = 1 AND id = max_id -- 最後の孤立奇数は据え置き\n", + " THEN id\n", + " ELSE ((id - 1) # 1) + 1 -- それ以外は 1↔2, 3↔4 を一発変換\n", + " END AS id,\n", + " student\n", + "FROM win\n", + "ORDER BY id; -- 仕様:id 昇順\n", + "\n", + "Runtime 179 ms\n", + "Beats 70.12%\n", + "\n", + "```\n", + "\n", + "### 代替(CTE不要・定数サブクエリ)\n", + "\n", + "> ウィンドウを避け、`MAX(id)` を 1 回だけ取得して全行に結合。`% 2` より **`& 1`** の方が軽量です。\n", + "\n", + "```sql\n", + "SELECT\n", + " CASE\n", + " WHEN (s.id & 1) = 1 AND s.id = m.max_id THEN s.id\n", + " ELSE ((s.id - 1) # 1) + 1\n", + " END AS id,\n", + " s.student\n", + "FROM seat AS s\n", + "CROSS JOIN (SELECT MAX(id) AS max_id FROM seat) AS m\n", + "ORDER BY id;\n", + "\n", + "Runtime 188 ms\n", + "Beats 49.06%\n", + "\n", + "```\n", + "\n", + "## 3) 要点解説\n", + "\n", + "* **発想**: 「2 人 1 組の入替」は **最下位ビットのトグル**で表現できる\n", + "\n", + " * 0 始まりに直す → `id-1`\n", + " * LSB を XOR で切替 → `(id-1) # 1`\n", + " * 1 始まりに戻す → `+1`\n", + "* **末尾の孤立奇数**のみ例外なので、`MAX(id)` と `(id & 1)` の組み合わせでガード\n", + "* **演算の軽量化**: `MOD(id,2)` や `id % 2` より **`id & 1`** の方が軽い傾向\n", + "* **並び順**: 問題仕様が「id 昇順」なので、最終 `ORDER BY id` を明示\n", + "\n", + "## 4) 計算量(概算)\n", + "\n", + "* フルスキャン主体で **O(N)**\n", + "\n", + " * `MAX(id) OVER ()` は 1 パス集約の伝播\n", + " * 代替版の `CROSS JOIN (SELECT MAX(id))` も 1 回評価の定数派生表\n", + "* ビット演算は定数時間で、分岐は 1 回のみ\n", + "\n", + "## 5) 図解(Mermaid 超保守版)\n", + "\n", + "```mermaid\n", + "flowchart TD\n", + " A[Seat 入力] --> B[\"MAX(id) を取得\"]\n", + " B --> C[\"LSB トグルで 1↔2, 3↔4\"]\n", + " C --> D[最後の孤立奇数を据え置き]\n", + " D --> E[id 昇順で整列]\n", + " E --> F[出力 id, student]\n", + "```\n", + "\n", + "この問題は**最終的に「並べ替えが必須」**(新しい `id` は元の `id` と同一順序にならない)なので、どんな書き方でも最終段で **`ORDER BY` によるソートコスト O(N log N)** は避けられません。したがって 179ms はかなり健闘値。ここから伸ばせるとすれば「微々たるマイクロ最適化」と「無駄な再計算を減らす」くらいです。\n", + "\n", + "以下、改善の余地とおすすめ最終形です。\n", + "\n", + "---\n", + "\n", + "## ちょい改善ポイント\n", + "\n", + "1. **偶奇判定の再計算を避ける**\n", + "\n", + "* `(id & 1)` を何度も評価しないように、サブクエリで一度だけ求めて使い回し。\n", + "\n", + "2. **ガード条件を最小化**\n", + "\n", + "* 例外は「`max_id` が奇数かつ `id = max_id`」の 1 ケースのみ。\n", + " → それ以外は XOR で一発変換:`((id - 1) # 1) + 1`\n", + "\n", + "3. **`MAX(id)` は 1 パスで全体に配る**\n", + "\n", + "* PG 16 ならウィンドウ `MAX(id) OVER ()` は十分速いです(CTE はデフォでインライン化)。\n", + " 実測でも CTE 版が速いのでそのままでOK。\n", + "\n", + "---\n", + "\n", + "## 推奨クエリ(最終形:分岐・再計算を最小化)\n", + "\n", + "```sql\n", + "WITH s AS (\n", + " SELECT\n", + " id,\n", + " student,\n", + " (id & 1) AS odd, -- 偶奇を一度だけ計算\n", + " MAX(id) OVER () AS max_id -- 全体最大を 1 パスで配る\n", + " FROM seat\n", + ")\n", + "SELECT\n", + " CASE\n", + " WHEN odd = 1 AND id = max_id -- 末尾の孤立奇数だけ据え置き\n", + " THEN id\n", + " ELSE ((id - 1) # 1) + 1 -- それ以外は 1↔2, 3↔4 を一発変換\n", + " END AS id,\n", + " student\n", + "FROM s\n", + "ORDER BY id; -- 問題仕様:id 昇順\n", + "\n", + "Runtime 180 ms\n", + "Beats 67.29%\n", + "\n", + "```\n", + "\n", + "### 期待\n", + "\n", + "* あなたの **179ms** と同等か、わずかに良化する可能性。\n", + "* これ以上は並べ替えコストがボトルネックなので、**劇的改善は見込みづらい**です。\n", + "\n", + "---\n", + "\n", + "## 他の選択肢(状況依存で僅差)\n", + "\n", + "* **定数サブクエリ JOIN 版**(`CROSS JOIN (SELECT MAX(id))`)\n", + " 環境次第で CTE より速いこともありますが、あなたの計測では CTE 版が優勢。維持でOK。\n", + "* **`UNION ALL` で 3 区分**(奇数・偶数・末尾奇数)\n", + " 結局、最終の `ORDER BY id` は必要なので、多くのケースで優位にはなりません。\n", + "\n", + "---\n", + "\n", + "## まとめ\n", + "\n", + "* この課題は**並べ替えが支配的**。今の 179ms は上出来です。\n", + "* 上の最終形(偶奇の事前計算+XOR一発+最小ガード)にしておけば、**読みやすさと軽さのバランス**が最良クラス。\n", + "* さらに詰める余地は実運用なら「ワークメモリや並列度(`work_mem`/`max_parallel_workers_per_gather`)」ですが、オンライン判定環境では調整不可なので、SQLの形としてはここが完成形です。\n", + "\n" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}