From 5b4171d104d42accc0c7cc38b2bdf871d56f0236 Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Fri, 3 Oct 2025 15:41:09 +0900 Subject: [PATCH 1/2] =?UTF-8?q?leetcode=202.=20Add=20Two=20Numbers=20=20?= =?UTF-8?q?=E5=90=8C=E6=99=82=E8=B5=B0=E6=9F=BB=EF=BC=8Bcarry=20=E4=BC=9D?= =?UTF-8?q?=E6=90=AC=EF=BC=88=E7=95=AA=E5=85=B5=EF=BC=8B=E3=83=86=E3=83=BC?= =?UTF-8?q?=E3=83=AB=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2. Add Two Numbers/AddTwoNumbers.ts | 47 +- .../2. Add Two Numbers/Claude/README.html | 1605 +++++++++++++++++ .../2. Add Two Numbers/Claude/README.md | 351 ++++ .../2. Add Two Numbers/GPT/AddTwoNumber.py | 129 ++ .../2. Add Two Numbers/GPT/AddTwoNumbers.js | 191 ++ .../GPT/AddTwoNumbers_refactored.ts | 161 ++ .../leetcode/2. Add Two Numbers/GPT/README.md | 238 +++ .../leetcode/2. Add Two Numbers/README.md | 180 +- 8 files changed, 2803 insertions(+), 99 deletions(-) create mode 100644 DataStructures/LinkedLists/leetcode/2. Add Two Numbers/Claude/README.html create mode 100644 DataStructures/LinkedLists/leetcode/2. Add Two Numbers/Claude/README.md create mode 100644 DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumber.py create mode 100644 DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumbers.js create mode 100644 DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumbers_refactored.ts create mode 100644 DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/README.md diff --git a/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/AddTwoNumbers.ts b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/AddTwoNumbers.ts index 7a3b7018..1ded0b02 100644 --- a/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/AddTwoNumbers.ts +++ b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/AddTwoNumbers.ts @@ -1,30 +1,35 @@ +export {}; + class ListNode { - val: number; - next: ListNode | null; + val: number; + next: ListNode | null; - constructor(val?: number, next?: ListNode | null) { - this.val = val ?? 0; - this.next = next ?? null; - } + constructor(val?: number, next?: ListNode | null) { + this.val = val ?? 0; + this.next = next ?? null; + } } -function addTwoNumbers(l1: ListNode | null, l2: ListNode | null): ListNode | null { - const dummyHead = new ListNode(0); - let current = dummyHead; - let carry = 0; +function addTwoNumbers( + l1: ListNode | null, + l2: ListNode | null +): ListNode | null { + const dummyHead = new ListNode(0); + let current = dummyHead; + let carry = 0; - while (l1 !== null || l2 !== null || carry > 0) { - const val1 = l1?.val ?? 0; - const val2 = l2?.val ?? 0; - const sum = val1 + val2 + carry; + while (l1 !== null || l2 !== null || carry > 0) { + const val1 = l1?.val ?? 0; + const val2 = l2?.val ?? 0; + const sum = val1 + val2 + carry; - carry = Math.floor(sum / 10); - current.next = new ListNode(sum % 10); - current = current.next; + carry = Math.floor(sum / 10); + current.next = new ListNode(sum % 10); + current = current.next; - l1 = l1?.next ?? null; - l2 = l2?.next ?? null; - } + l1 = l1?.next ?? null; + l2 = l2?.next ?? null; + } - return dummyHead.next; + return dummyHead.next; } diff --git a/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/Claude/README.html b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/Claude/README.html new file mode 100644 index 00000000..7006ac29 --- /dev/null +++ b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/Claude/README.html @@ -0,0 +1,1605 @@ + + + + + + Add Two Numbers - 逆順連結リスト加算 + + + + + + + + + + + + + + + + + + + + + +
+
+

+ Add Two Numbers +

+

+ LeetCode 2 - 逆順連結リスト加算(同時走査 + 繰り上がり伝搬) +

+ + + +
+
+ +
+ +
+
+

+ 1 + 概要 +

+ +
+

+ 問題: + 2つの非空連結リストで表された非負整数を加算し、結果を連結リストで返す。各ノードは単一の桁を保持し、桁は逆順で格納されている。 +

+ +
+

例:

+ l1 = [2,4,3], l2 = [5,6,4] → [7,0,8] +

(342 + 465 = 807)

+
+ +

+ アルゴリズム戦略: + 2つのリストを同時走査し、各桁の和と繰り上がり(carry)を計算。番兵ノードとテールポインタでO(1)追加を実現。 +

+ +
    +
  • + 時間計算量: O(n) - n = max(len(l1), len(l2)) +
  • +
  • + 空間計算量: O(1) - 出力ノードを除く補助空間 +
  • +
  • データ構造: ListNode(単方向連結リスト)
  • +
+
+
+
+ + +
+
+

+ 2 + ステップバイステップ解説 +

+ +
+
+
+ + +
+
+

+ 3 + Python実装(LeetCode形式) +

+ +
+

+ ポイント: + 番兵ノードで先頭処理を簡潔化、テールポインタでO(1)追加、非破壊的な実装 +

+
+ +
from __future__ import annotations
+from typing import Optional, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    class ListNode:
+        val: int
+        next: Optional[ListNode]
+        def __init__(self, val: int = 0, next: Optional[ListNode] = None) -> None: ...
+else:
+    try:
+        from leetcode_structures import ListNode  # type: ignore
+    except (ImportError, ModuleNotFoundError):
+        class ListNode:
+            __slots__ = ("val", "next")
+            def __init__(self, val: int = 0, next: Optional[ListNode] = None) -> None:
+                self.val = val
+                self.next = next
+
+
+class Solution:
+    """
+    LeetCode 2: Add Two Numbers
+
+    2つの逆順連結リストで表された非負整数を加算し、
+    結果を逆順連結リストで返す。
+
+    時間計算量: O(n) - n = max(len(l1), len(l2))
+    空間計算量: O(1) - 出力ノードを除く補助空間
+    """
+
+    def addTwoNumbers(
+        self,
+        l1: Optional[ListNode],
+        l2: Optional[ListNode]
+    ) -> Optional[ListNode]:
+        """
+        2つの逆順リストの和を逆順リストで返す。
+
+        Args:
+            l1: 第1の数の逆順連結リスト(最下位桁が先頭)
+            l2: 第2の数の逆順連結リスト(最下位桁が先頭)
+
+        Returns:
+            和を表す逆順連結リスト
+        """
+        # 番兵ノード: 先頭ノードの特別処理を排除
+        dummy: ListNode = ListNode(0)
+        tail: ListNode = dummy
+
+        # 走査ポインタと繰り上がり
+        p: Optional[ListNode] = l1
+        q: Optional[ListNode] = l2
+        carry: int = 0
+
+        # いずれかのリストが残るか、繰り上がりが残る限り処理
+        while p is not None or q is not None or carry != 0:
+            # 現在の桁の値を取得(リストが終了していれば0)
+            x: int = p.val if p is not None else 0
+            y: int = q.val if q is not None else 0
+
+            # 桁の和 + 繰り上がりを計算
+            sum_: int = x + y + carry
+            carry = sum_ // 10  # 新しい繰り上がり
+            digit: int = sum_ % 10  # 現在の桁の値
+
+            # 結果ノードを生成して末尾に追加
+            tail.next = ListNode(digit)
+            tail = tail.next
+
+            # ポインタを前進(存在する場合のみ)
+            if p is not None:
+                p = p.next
+            if q is not None:
+                q = q.next
+
+        # 番兵の次が実際の結果の先頭
+        return dummy.next
+
+
+ + +
+
+

+ 4 + アルゴリズムフローチャート +

+ +
+ + + + + 開始 + + + + + + + + + 初期化 + + + dummy, tail, p=l1, q=l2 + + + carry=0 + + + + + + + + + p or q or + + + carry? + + + + + + いいえ + + + + + + はい + + + + + + 値を取得 + + + x = p.val または 0, y = q.val または 0 + + + + + + + + + 計算 + + + sum = x + y + carry + + + + + + + + + 分割 + + + carry = sum // 10, digit = sum % 10 + + + + + + + + + 作成と追加 + + + tail.next = ListNode(digit) + + + + + + + + + 返却 + + + dummy.next + + + + + + + + + +
+

+ ループ条件でいずれかのポインタまたはcarryが存在する限り処理を継続。各反復で桁を計算し、新ノードを追加してポインタを前進。 +

+
+
+ + +
+
+

+ 5 + 計算量分析 +

+ +
+ +
+

+ 時間計算量 +

+

O(n)

+

+ n = max(len(l1), len(l2))
+ 各ノードを1回ずつ訪問し、定数時間の演算のみ実行。 +

+
+ + +
+

空間計算量

+

O(1)

+

+ 出力ノードを除く補助空間。番兵ノード、ポインタ変数のみ使用。入力リストは変更しない。 +

+
+
+ +
+

+ 効率性のポイント +

+
    +
  • + + 単一パス: 1回の走査で全処理完了 +
  • +
  • + + 定数空間: 追加の配列やスタック不要 +
  • +
  • + + 非破壊的: + 入力リストを変更しないため安全 +
  • +
  • + + スケーラブル: 桁数が増えても線形に対応 +
  • +
+
+
+
+
+ + + + + + + + + + + + + + diff --git a/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/Claude/README.md b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/Claude/README.md new file mode 100644 index 00000000..2ac49f14 --- /dev/null +++ b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/Claude/README.md @@ -0,0 +1,351 @@ +# Add Two Numbers - 逆順連結リスト加算 + + + +## 目次 + +- [概要](#overview) +- [アルゴリズム要点(TL;DR)](#tldr) +- [図解](#figures) +- [正しさのスケッチ](#correctness) +- [計算量](#complexity) +- [Python 実装](#impl) +- [CPython 最適化ポイント](#cpython) +- [エッジケースと検証観点](#edgecases) +- [FAQ](#faq) + +--- + +

概要

+ +**問題タイトル**: Add Two Numbers +**プラットフォーム/ID**: LeetCode 2 + +### 問題要約 + +2 つの **非空** 連結リスト(各ノードは単一の桁を保持)で表された非負整数が与えられる。桁は **逆順** で格納されている(例: `342` → `[2,4,3]`)。2 つの数を加算し、結果を同じく逆順の連結リストとして返す。 + +### 要件 + +- **正当性**: 各桁の加算と繰り上がり(carry)を正確に処理し、最終的な桁数が正しいこと +- **安定性**: 入力リストを破壊しない(非破壊的操作) +- **制約**: + - 各リストのノード数: `[1, 100]` + - 各ノードの値: `0 <= Node.val <= 9` + - 先頭ゼロは数値 0 以外では存在しない + +--- + +

アルゴリズム要点(TL;DR)

+ +- **戦略**: 2 つのリストを **同時走査** し、各桁の和 + 繰り上がり(carry)を計算。新しいノードを逐次生成して結果リストを構築 +- **データ構造**: + - **番兵(ダミー)ノード**: 先頭ノードの特別処理を排除し、コードを簡潔化 + - **テールポインタ**: O(1)で末尾追加を実現 +- **計算量**: + - **時間**: O(n)(n = max(len(l1), len(l2))) + - **空間**: O(1)(出力ノードを除く補助空間) +- **メモリ要約**: 新規ノードは結果リスト用のみ。追加の配列やスタックは不要 + +--- + +

図解

+ +### フローチャート + +```mermaid +flowchart TD + Start[Start solve] --> Init[Initialize dummy head and tail] + Init --> Setup[Set p=l1, q=l2, carry=0] + Setup --> Loop{p or q or carry exists} + Loop -- No --> Return[Return dummy.next] + Loop -- Yes --> GetVals[Get x=p.val or 0, y=q.val or 0] + GetVals --> Calc[sum = x + y + carry] + Calc --> Split[carry = sum // 10, digit = sum % 10] + Split --> NewNode[Create new node with digit] + NewNode --> Append[tail.next = new node, tail = tail.next] + Append --> Advance[Advance p and q if not None] + Advance --> Loop +``` + +**説明**: ダミーヘッドを用意し、両リストと carry が全て処理されるまでループ。各ステップで桁の和を計算し、新ノードを末尾に追加。ポインタを前進させて次の桁へ。 + +### データフロー図 + +```mermaid +graph LR + subgraph Input + A[l1 nodes] --> C[Digit extraction] + B[l2 nodes] --> C + end + subgraph Processing + C --> D[Sum with carry] + D --> E[Split into digit and new carry] + E --> F[Create result node] + end + subgraph Output + F --> G[Append to result list] + G --> H[Final linked list] + end +``` + +**説明**: 各リストから桁を取り出し、carry と合算。結果を新ノードとして出力リストに逐次追加。最終的に逆順のまま完全な和を表すリストが得られる。 + +--- + +

正しさのスケッチ

+ +### 不変条件 + +- **ループ不変条件**: 各反復の開始時、`tail` は結果リストの最後のノードを指し、`carry` は前の桁からの繰り上がりを保持する +- `p` と `q` はそれぞれ未処理の最初のノード、または None を指す + +### 網羅性 + +- **ケース 1**: `p` と `q` が両方とも存在 → 両方の値と carry を加算 +- **ケース 2**: 一方のみ存在 → 存在する方の値 + carry(他方は 0 として扱う) +- **ケース 3**: 両方 None、carry のみ残存 → carry を最終桁として追加 +- ループ条件 `while p or q or carry` により、全てのケースが網羅される + +### 基底条件 + +- 両リストが空かつ carry が 0 になった時点でループ終了 +- 最小ケース(例: `l1=[0], l2=[0]`)でも正しく `[0]` を返す + +### 終了性 + +- 各反復で `p` と `q` を前進させるため、有限回(最大 `max(len(l1), len(l2)) + 1` 回)で必ず終了 +- 最悪でも繰り上がりが 1 桁追加される程度で収束 + +--- + +

計算量

+ +### 時間計算量 + +- **O(n)**: n = max(len(l1), len(l2)) +- 各ノードを 1 回ずつ訪問し、定数時間の演算(加算、除算、剰余)のみ実行 + +### 空間計算量 + +- **O(1)**: 出力ノードを除く補助空間 +- 番兵ノード 1 個、ポインタ変数(`p`, `q`, `tail`, `carry`)のみ使用 +- 出力リストのノード数は O(n)だが、これは問題の性質上必須 + +### 方式比較 + +| アプローチ | 時間 | 空間(補助) | 備考 | +| ---------------------------- | ---- | ------------ | ---------------------------------- | +| **同時走査+carry(本実装)** | O(n) | O(1) | 最もシンプルで効率的 | +| 配列展開 → 数値化 → 再構築 | O(n) | O(n) | 桁数が多い場合に BigInt 対応が冗長 | +| 再帰による桁加算 | O(n) | O(n) | 再帰深度により非効率 | + +--- + +

Python 実装

+ +```python +from __future__ import annotations +from typing import Optional, TYPE_CHECKING + +# LeetCode環境ではListNodeが提供されるが、単独実行用にフォールバック定義 +if TYPE_CHECKING: + # 型チェック時はこちらを参照(Pylance用) + class ListNode: + val: int + next: Optional[ListNode] + def __init__(self, val: int = 0, next: Optional[ListNode] = None) -> None: ... +else: + # 実行時はLeetCodeの定義を優先、なければ軽量実装 + try: + from leetcode_structures import ListNode # type: ignore + except (ImportError, ModuleNotFoundError): + class ListNode: + __slots__ = ("val", "next") + def __init__(self, val: int = 0, next: Optional[ListNode] = None) -> None: + self.val = val + self.next = next + + +class Solution: + """ + LeetCode 2: Add Two Numbers + + 2つの逆順連結リストで表された非負整数を加算し、 + 結果を逆順連結リストで返す。 + + 時間計算量: O(n) - n = max(len(l1), len(l2)) + 空間計算量: O(1) - 出力ノードを除く補助空間 + """ + + def addTwoNumbers( + self, + l1: Optional[ListNode], + l2: Optional[ListNode] + ) -> Optional[ListNode]: + """ + 2つの逆順リストの和を逆順リストで返す。 + + Args: + l1: 第1の数の逆順連結リスト(最下位桁が先頭) + l2: 第2の数の逆順連結リスト(最下位桁が先頭) + + Returns: + 和を表す逆順連結リスト + + Examples: + >>> # l1=[2,4,3], l2=[5,6,4] -> [7,0,8] (342+465=807) + >>> # l1=[9,9,9,9,9,9,9], l2=[9,9,9,9] -> [8,9,9,9,0,0,0,1] + """ + # 番兵ノード: 先頭ノードの特別処理を排除 + dummy: ListNode = ListNode(0) + tail: ListNode = dummy + + # 走査ポインタと繰り上がり + p: Optional[ListNode] = l1 + q: Optional[ListNode] = l2 + carry: int = 0 + + # いずれかのリストが残るか、繰り上がりが残る限り処理 + while p is not None or q is not None or carry != 0: + # 現在の桁の値を取得(リストが終了していれば0) + x: int = p.val if p is not None else 0 + y: int = q.val if q is not None else 0 + + # 桁の和 + 繰り上がりを計算 + sum_: int = x + y + carry + carry = sum_ // 10 # 新しい繰り上がり + digit: int = sum_ % 10 # 現在の桁の値 + + # 結果ノードを生成して末尾に追加 + tail.next = ListNode(digit) + tail = tail.next + + # ポインタを前進(存在する場合のみ) + if p is not None: + p = p.next + if q is not None: + q = q.next + + # 番兵の次が実際の結果の先頭 + return dummy.next +``` + +### 実装のポイント + +1. **番兵ノード**: `dummy` により先頭ノードの特別処理が不要になり、コードが簡潔化 +2. **テールポインタ**: `tail` で常に最後のノードを追跡し、O(1)追加を実現 +3. **Null 安全**: `p is not None` チェックで None ポインタ参照を回避 +4. **型注釈**: `Optional[ListNode]` で静的解析ツール(Pylance)に対応 +5. **非破壊**: 入力リストは変更せず、新しいノードのみ生成 + +--- + +

CPython最適化ポイント

+ +### 実装済みの最適化 + +1. **ループの最小化** + + - 単一の `while` ループで全処理を完結 + - 分岐は最小限(`is not None` チェックのみ) + +2. **属性アクセスの削減** + + - ループ内で `p.val`, `q.val` を一度だけ読み取り、ローカル変数に格納 + - 繰り返しの属性参照を回避 + +3. **オブジェクト生成の抑制** + + - 各反復で `ListNode` を 1 個のみ生成 + - 中間リスト、タプル、辞書などは使用しない + +4. **整数演算の効率化** + - `//` と `%` による除算・剰余は、CPython で高度に最適化済み + - `divmod()` も選択肢だが、可読性重視で個別演算を使用 + +### さらなる最適化の余地(状況依存) + +- **数値変換アプローチ**: リストを整数に変換して加算し、再度リストに戻す方法もあるが、桁数が多い場合(100 桁)は `int` から `ListNode` への変換コストが高い +- **再帰の回避**: 本実装は既に反復的であり、スタックオーバーフローのリスクがない + +### メモリ効率 + +- `__slots__` を `ListNode` に追加すれば、ノードあたりのメモリを削減可能(ただし LeetCode 環境では通常不要) + +--- + +

エッジケースと検証観点

+ +### 主要なエッジケース + +1. **両方ともゼロ** + + - 入力: `l1=[0], l2=[0]` + - 出力: `[0]` + - 確認: 最小ケースで正常動作 + +2. **桁あふれ(繰り上がりが最終桁を超える)** + + - 入力: `l1=[9,9,9,9,9,9,9], l2=[9,9,9,9]` + - 出力: `[8,9,9,9,0,0,0,1]` + - 確認: 最終的な繰り上がりが新しいノードとして追加される + +3. **長さが大きく異なる** + + - 入力: `l1=[2,4,3], l2=[5,6,4,9,9]` + - 出力: `[7,0,8,9,9]` + - 確認: 短い方が終了してもループが継続し、長い方を正しく処理 + +4. **片方が単一ノード** + + - 入力: `l1=[5], l2=[5]` + - 出力: `[0,1]` + - 確認: 繰り上がりが発生するケース + +5. **最大長(制約の境界)** + - 入力: 100 ノードずつ + - 確認: パフォーマンスが線形であること、メモリが安定していること + +### 検証観点 + +- **正当性**: 全ての桁が正しく加算され、繰り上がりが正確に伝搬されているか +- **安定性**: 入力リストが変更されていないか(非破壊性) +- **境界値**: 最小(1 ノード)から最大(100 ノード)まで動作するか +- **型安全性**: Pylance で警告が出ないか(`Optional` の適切な処理) + +--- + +

FAQ

+ +### Q1: なぜ番兵(ダミー)ノードを使うのか? + +**A**: 先頭ノードの特別処理を避けるため。番兵がないと、最初のノードを作成する際に `if` 文が必要になり、コードが複雑化する。番兵により全ノードを統一的に処理できる。 + +### Q2: `divmod()` を使わない理由は? + +**A**: `carry, digit = divmod(sum_, 10)` も有効だが、`//` と `%` の方が意図が明確で読みやすい。パフォーマンス差はほぼない。 + +### Q3: 入力が `None` の場合は考慮する必要があるか? + +**A**: LeetCode の制約では「非空リスト」が保証されているため、`l1` や `l2` が `None` になることはない。ただし、型注釈は `Optional[ListNode]` にして LeetCode のシグネチャに合わせている。 + +### Q4: 再帰的な実装の方がシンプルではないか? + +**A**: 再帰も可能だが、以下の理由で反復的実装を推奨: + +- **スタック効率**: 100 ノードの深さでもスタックを消費しない +- **CPython 最適化**: ループの方が最適化されやすい +- **可読性**: 初学者にも理解しやすい + +### Q5: 出力リストを逆順にするのではなく、順序を反転させる必要はないか? + +**A**: 問題の要件では、出力も **逆順**(最下位桁が先頭)で返すため、反転は不要。入力と同じ順序規則で出力する。 + +### Q6: より大きな桁数(例: 1000 桁)でも動作するか? + +**A**: アルゴリズム自体はスケーラブルだが、LeetCode の制約は最大 100 ノード。1000 桁でも理論的には動作するが、数値変換アプローチの方が効率的になる可能性がある(ただし、本実装は依然として O(n)で動作する)。 + +--- + +[トップに戻る](#top) diff --git a/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumber.py b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumber.py new file mode 100644 index 00000000..fe0dc54a --- /dev/null +++ b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumber.py @@ -0,0 +1,129 @@ +# ## 1. 問題の分析 + +# ### 競技プログラミング視点での分析 + +# * **最速手法**: 2つの連結リストを**同時走査**し、各桁の和を求めて**繰り上がり(carry)**を伝搬。出力は先頭に**番兵(ダミー)ノード**を置き、テールポインタでO(1)追加。 +# * **計算量**: 1パスで **O(n)**(nは長い方のノード数)。補助配列等を使わないため追加空間は **O(1)**(出力ノードを除く)。 + +# ### 業務開発視点での分析 + +# * **可読性/保守性**: 先頭ノードの特例分岐を番兵で排除し、`while (p or q or carry)` の単純ループに集約。安全な型注釈で後工程の静的解析(Pylance)にも強い。 +# * **エラーハンドリング**: LeetCodeでは入力前提が保証されるため実行時例外は不要。堅牢性を上げたい場合は**任意の軽量バリデーション**を外部/前段で行う。 + +# ### Python特有考慮 + +# * **CPython最適化**: 単純な`while`ループ+整数演算のみ。関数呼び出し・コンテナ生成を最小化。`//` と `%` を用いる。 +# * **GIL影響**: CPU軽量な逐次計算であり影響は無視可能。 +# * **内蔵データ構造**: 追加構造は使わず、`ListNode`と整数のみで完結。 + +# --- + +# ## 2. アルゴリズム比較表 + +# | アプローチ | 時間計算量 | 空間計算量 | Python実装コスト | 可読性 | 標準ライブラリ活用 | CPython最適化 | 備考 | +# | --------------------------- | -------- | ---------- | ----------- | --- | --------- | ---------- | --------------- | +# | **方法A**: 同時走査+carry(番兵+テール) | **O(n)** | **O(1)** | 低 | 高 | 不要 | 適 | 最短・定番 | +# | 方法B: 配列/文字列へ展開→数値化→再構築 | O(n) | O(n) | 中 | 中 | 不要 | 不適 | 桁溢れ/BigInt処理が冗長 | +# | 方法C: 再帰で桁加算 | O(n) | O(n)(再帰深さ) | 低 | 中 | 不要 | 不適 | 深い入力で非効率 | + +# **採用:** 方法A(同時走査+carry) + +# --- + +# ## 3. Python特有最適化ポイント + +# * **ループ最適化**: `while p or q or carry:` の単純分岐で分配律のように処理。ローカル変数束縛を活用し属性アクセス回数を抑制。 +# * **オブジェクト生成の抑制**: 毎ステップ新ノード1個のみ。中間リストやタプルを作らない。 +# * **整数演算**: `carry, digit = divmod(sum_, 10)` でも良いが、読みやすさと速度のバランスで `carry = sum_ // 10; digit = sum_ % 10` を使用。 + +# --- + +# ## 4. 実装コード(LeetCode形式 / Class) + +# ```python +from __future__ import annotations +from typing import Optional + + +# LeetCode 環境では既に定義されていることが多いが、単独ファイルでも動くよう同等の定義を含める +class ListNode: + def __init__(self, val: int = 0, next: Optional["ListNode"] = None) -> None: + self.val: int = val + self.next: Optional["ListNode"] = next + + +class Solution: + """ + Add Two Numbers + Two non-empty linked lists representing non-negative integers (digits in reverse order). + Return the sum as a linked list (also in reverse order). + + 競技向け: 単一パス O(n), 追加空間 O(1)(出力除く) + Pylance配慮: Optional/型注釈を厳格に付与 + """ + + def addTwoNumbers( + self, l1: Optional[ListNode], l2: Optional[ListNode] + ) -> Optional[ListNode]: + """ + 2つの逆順リストの和を逆順リストで返す(非破壊) + + Args: + l1: 先頭ノード(None不可想定だが、型はOptionalでLeetCode互換) + l2: 先頭ノード(同上) + + Returns: + 結果リストの先頭ノード + + Complexity: + Time: O(n) (nは長い方のリスト長) + Space: O(1) (出力ノードを除く) + """ + # ダミーヘッド(番兵)+テールポインタで先頭分岐を排除 + dummy: ListNode = ListNode(0) + tail: ListNode = dummy + + p: Optional[ListNode] = l1 + q: Optional[ListNode] = l2 + carry: int = 0 + + # いずれかが残るか、carryが残る限り処理 + while p is not None or q is not None or carry != 0: + x: int = p.val if p is not None else 0 + y: int = q.val if q is not None else 0 + + sum_: int = x + y + carry + carry = sum_ // 10 + digit: int = sum_ % 10 + + tail.next = ListNode(digit) + tail = tail.next # type: ignore[assignment] # tail.next は ListNode であることが文脈上保証される + + if p is not None: + p = p.next + if q is not None: + q = q.next + + # ここまでで next は必ず None か ListNode のどちらか + + return dummy.next + + +# ``` + +# --- + +# ## 5. 検証(方針のみ・テストコード不要) + +# * **境界**: `l1=[0], l2=[0]` → `[0]` +# * **桁あふれ**: `l1=[9,9,9], l2=[1]` → `[0,0,0,1]` +# * **長さ不一致**: `l1=[2,4,3], l2=[5,6,4,9,9]` → 繰り上がり含め正しく連結 +# * **最大長**: 100桁同士でも1パスで完了(追加空間O(1)) + +# --- + +# ### まとめ + +# * **方法A(同時走査+carry)**を採用:**O(n)** / **O(1)**、実装簡潔・堅牢。 +# * **Pylance配慮**の厳密な型注釈(`Optional[ListNode]`、変数の型明示)で静的解析も問題なし。 +# * **LeetCode互換のClass形式**で、そのまま提出可能です。 diff --git a/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumbers.js b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumbers.js new file mode 100644 index 00000000..49017782 --- /dev/null +++ b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumbers.js @@ -0,0 +1,191 @@ +// # 0. 実行環境 + +// * **Language/Runtime:** JavaScript (Node.js v18+) +// * **Module:** **CommonJS** +// * **外部ライブラリ:** なし(Node 標準のみ) +// * **CI 前提:** `node solution.js` 単体で実行可能(入出力コードは含めません/テストコード不要) + +// --- + +// # 1. 問題 + +// 二つの非空単方向連結リスト `l1`, `l2` は逆順に格納された非負整数を表す。各ノードは 1 桁(0–9)を保持する。二数の和を同じ形式の連結リストで返せ。 + +// * 例1: `l1=[2,4,3], l2=[5,6,4] -> [7,0,8]` (342 + 465 = 807) +// * 例2: `l1=[0], l2=[0] -> [0]` +// * 例3: `l1=[9,9,9,9,9,9,9], l2=[9,9,9,9] -> [8,9,9,9,0,0,0,1]` +// * 制約: + +// * 各リストのノード数は `[1, 100]` +// * `0 <= Node.val <= 9` +// * 先頭に不要な 0 は無い(数 0 自体を除く) + +// --- + +// # 2. 解析 + +// ## 2.1 競技プログラミング視点 + +// * **速度最優先アプローチ:** 同時走査し、桁ごとに `carry` を伝搬。1 パス `O(n)`、追加配列不要。ダミーヘッドで末尾追加を `O(1)` に。 +// * **メモリ最小化:** 破壊的更新を避けつつも新規ノードは必要最小限(合計桁数 + 1 まで)。ストリーム的に前から生成。 + +// ## 2.2 業務開発視点 + +// * **保守性・可読性:** `validateList` を分離し、JSDoc と型ガードで意図を明確化。早期例外でバグを顕在化。 +// * **エラーハンドリング:** + +// * 引数が `ListNode` 系でない → `TypeError` +// * 値が整数[0,9]外/ノード数>100 → `RangeError` +// * (LeetCode では厳格チェックは不要だが、本回答要件に従い実装) + +// ## 2.3 JavaScript特有の考慮 + +// * **V8最適化:** ループは `while`/`for`、プロパティ形状は固定(`val`,`next`のみ)。`ListNode` コンストラクタは一定形状を維持。 +// * **GC対策:** 一時オブジェクト生成を抑え、ダミーヘッド + テールポインタのみ。クロージャ不使用。 +// * **配列操作特性:** 配列は不使用(連結リスト直接操作)。`push/pop` 等のコストは関与しない。 + +// --- + +// # 3. アルゴリズム比較 + +// | アプローチ | 時間計算量 | 空間計算量 | JS実装コスト | 可読性 | 備考 | +// | -------------------------- | --------------- | ------------: | ------: | --- | -------------------------- | +// | **方法A**(同時走査 + 桁和 + 繰り上がり) | **O(n)** | **O(1)** 追加 | 低 | 中〜高 | ダミーヘッドで実装容易・最速 | +// | 方法B(配列へ展開→数値化→加算→再展開) | O(n) ただし多オブジェクト | O(n) | 中 | 低 | 桁数多で BigInt/桁溢れ考慮が必要、無駄が多い | +// | 方法C(再帰でペア加算) | O(n) | O(n)(コールスタック) | 低 | 中 | 深い再帰でスタック増、不要 | + +// **採用:** 方法A(最短・省メモリ・V8に優しい) + +// --- + +// # 4. 採用方針 + +// * **選択:** 方法A(同時走査 + carry) +// * **理由:** 単一パス `O(n)` / 追加メモリ `O(1)`、実装簡潔、定番でバグ少。 +// * **保守性:** 検証を関数分離、JSDocと例外で契約を明確化。 +// * **JS最適化の具体点:** + +// * `while (p || q || carry)` の単純ループ +// * `new ListNode(sum % 10)` の一定形状オブジェクト +// * 余計な配列・クロージャ生成を避ける + +// --- + +// # 5. コード実装(solution.js) + +// ```js +"use strict"; + +/** + * 単方向連結リストノード(LeetCode 互換) + * @constructor + * @param {number} val + * @param {ListNode|null} [next=null] + */ +function ListNode(val, next = null) { + this.val = val; + this.next = next; +} + +/** + * 入力リストの軽量検証(型・値域・長さ) + * 仕様に従い TypeError / RangeError を投げる + * @param {ListNode} head + * @param {string} name + * @throws {TypeError|RangeError} + */ +function validateList(head, name) { + // 型 + if (head == null || typeof head !== "object") { + throw new TypeError(`${name} must be a ListNode`); + } + // 走査してノード数と値域 + let cnt = 0; + let node = head; + while (node) { + cnt++; + if (cnt > 100) { + throw new RangeError(`${name} length exceeds 100`); + } + const v = node.val; + if (!Number.isInteger(v)) { + throw new TypeError(`${name} contains non-integer value`); + } + if (v < 0 || v > 9) { + throw new RangeError(`${name} contains digit out of [0,9]`); + } + // 次ポインタ型の緩やかな検証 + if (node.next !== null && typeof node.next !== "object") { + throw new TypeError(`${name}.next must be ListNode or null`); + } + node = node.next; + } + if (cnt === 0) { + throw new RangeError(`${name} must be non-empty`); + } +} + +/** + * Add Two Numbers(非破壊的:入力リストは変更しない) + * + * @function addTwoNumbers + * @param {ListNode} l1 - 逆順の数を表す連結リスト1 + * @param {ListNode} l2 - 逆順の数を表す連結リスト2 + * @returns {ListNode} 和を表す新しい連結リスト(逆順) + * @throws {TypeError} 引数型が不正 + * @throws {RangeError} 値域/長さが制約超過 + * @complexity 時間計算量 O(n)、空間計算量 O(1)(出力ノードを除く) + */ +function addTwoNumbers(l1, l2) { + // ---- 入力検証(ホットパス外:最初に一度だけ)---- + validateList(l1, "l1"); + validateList(l2, "l2"); + + // ---- 本処理(単一パス)---- + let p = l1; + let q = l2; + let carry = 0; + + const dummy = new ListNode(0); + let tail = dummy; + + while (p || q || carry !== 0) { + const x = p ? p.val : 0; + const y = q ? q.val : 0; + + // 桁和 + 繰り上がり + const sum = x + y + carry; + carry = (sum / 10) | 0; // 10 での整数除算(ビット演算で高速化) + const digit = sum % 10; + + // 新規ノードを一つだけ生成し末尾に繋ぐ + tail.next = new ListNode(digit); + tail = tail.next; + + // 次へ + if (p) p = p.next; + if (q) q = q.next; + } + + return dummy.next; +} + +module.exports = { ListNode, addTwoNumbers }; +// ``` + +// --- + +// # 5. 追加メモ(JS最適化チェックリスト) + +// * ループは `while` の単純形。`forEach` / `map` は不使用(不要なコールバック割当を回避)。 +// * 一時配列・中間オブジェクトは最小限(ノード生成のみ)。 +// * `ListNode` は固定プロパティ順(`val` → `next`)で hidden class を安定化。 +// * 例外は開始時の検証で早期に投げ、ホットパスから排除。 +// * 64bit 超桁の数値演算は配列化や BigInt 不要(桁ごと加算のため)。 +// * 入力は**非破壊**(Pure Function)。出力は新規リストのみ。 + +// --- + +// **補足:** + +// * LeetCode 環境では `ListNode` 定義が既に存在することがありますが、上記は**単体ファイル**としても読めるよう同名クラスを含めています。LeetCode へ貼り付ける際は `module.exports` 行を削除するだけでそのまま動作します(求められる関数名は `addTwoNumbers`)。 diff --git a/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumbers_refactored.ts b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumbers_refactored.ts new file mode 100644 index 00000000..94c36f8a --- /dev/null +++ b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/AddTwoNumbers_refactored.ts @@ -0,0 +1,161 @@ +// ## 1. 問題の分析 + +// ### 競技プログラミング視点での分析 + +// * **最速アプローチ:** 2 つの連結リストを同時走査し、各桁の和+繰り上がり(`carry`)を管理して新しい結果リストを前から構築。1 パス **O(n)**、末尾追加はテールポインタで **O(1)**。 +// * **メモリ最小化:** 入力リストは非破壊、出力は必要最小限の新規ノード(最大で長い方の長さ + 1)。補助配列や文字列化は行わない。 + +// ### 業務開発視点での分析 + +// * **型安全性:** `ListNode` を厳密に定義(`val: number`, `next: ListNode | null`)。入出力は `ListNode | null` の組み合わせを許容(LeetCode 互換)。 +// * **可読性/保守性:** ダミーノード(番兵)+テールポインタで実装を単純化。`while (p || q || carry)` の明快なループ。補助関数での過剰分割は行わず、ホットパスに集中。 +// * **エラーハンドリング:** LeetCode 前提では実行時例外は通常不要だが、**本回答では要件に従い軽量ガード**(値域の前提を壊す入力が来た際に `RangeError` を投げるチェックをオプション的に実装可能)。デフォルトでは前提を信頼し、ホットパスを汚さない。 + +// ### TypeScript特有の考慮点 + +// * **型推論:** `number` と `null` の合流型を最小化し、`digit`/`carry` は `number` で安定。 +// * **コンパイル時最適化:** プロパティ形状固定(`val`, `next`)で隠れクラス安定化に貢献。`Math.floor` によりビット演算の 32bit 化による不意の型崩れを避ける。 +// * **null 安全:** 走査ポインタ `p`/`q` は `ListNode | null`。都度の三項演算子で `0` 供給し、`next` 進行は null チェック。 + +// --- + +// ## 2. アルゴリズムアプローチ比較 + +// | アプローチ | 時間計算量 | 空間計算量 | TS実装コスト | 型安全性 | 可読性 | 備考 | +// | ------------------------------ | -------- | -------------- | ------: | ---: | --: | ----------------------- | +// | **方法A**: 同時走査+carry 伝搬(番兵+テール) | **O(n)** | **O(1)**(出力除く) | 低 | 高 | 高 | 定番・最速・最少メモリ | +// | 方法B: 配列/文字列へ展開→数値加算→再構築 | O(n) | O(n) | 中 | 中 | 中 | 桁オーバーフローや BigInt 混入で複雑化 | +// | 方法C: 再帰的に桁加算 | O(n) | O(n)(再帰深さ) | 低 | 高 | 中 | スタック使用で不利、利点少 | + +// **採用: 方法A**(速度・メモリ・実装容易性のバランスが最良) + +// --- + +// ## 3. 選択したアルゴリズムと理由 + +// * **選択したアプローチ:** 方法A(同時走査+carry) +// * **理由:** + +// * **計算量:** 時間 **O(n)** / 追加空間 **O(1)**(出力ノードを除く) +// * **型安全:** `ListNode` の構造を固定化し、`null` 分岐を明確化 +// * **保守性/可読性:** ダミーヘッドで特例除去(先頭ノードの分岐不要) +// * **TypeScript特有の最適化ポイント:** + +// * `ListNode` を `class` で定義し、プロパティ順を固定 +// * `const`/`let` の最小使用域、三項演算で `null` を数値 `0` に安全変換 +// * ビット演算は使用せず `Math.floor` により値域の意図を明確化 + +// --- + +// ## 4. 実装コード(TypeScript / ESM / LeetCode 互換) + +// > **メモ:** LeetCode の TypeScript 環境と互換のため、関数名/シグネチャは既定形に合わせています。**Pure function(入力リストは不変)**。 +// > Node.js v18 で ESM を想定する場合、本ファイルを `add-two-numbers.ts` としてコンパイルして利用できます(外部ライブラリ不使用)。 + +// ```ts +// ======================================================= +// Add Two Numbers - TypeScript (ESM) / LeetCode format +// Node.js v18 / External libraries: none +// Strict mode想定(tsconfigで "strict": true を推奨) +// ======================================================= + +export class ListNode { + val: number; + next: ListNode | null; + + constructor(val?: number, next?: ListNode | null) { + this.val = val ?? 0; + this.next = next ?? null; + } +} + +/** + * 2つの逆順リストで表された非負整数の和を、逆順リストで返す(非破壊) + * + * @param l1 - 第1引数リストの先頭ノード(null不可想定だが型は互換のため null 許容) + * @param l2 - 第2引数リストの先頭ノード(null不可想定だが型は互換のため null 許容) + * @returns 新しく生成された結果リストの先頭ノード + * @throws {RangeError} ノード値が 0–9 の範囲外(前提破り入力)を検出した場合 + * @complexity Time: O(n), Space: O(1) ※出力ノードを除く + */ +export function addTwoNumbers( + l1: ListNode | null, + l2: ListNode | null +): ListNode | null { + // --- (任意)軽量バリデーション:LeetCodeの前提を壊す入力を明示検出 --- + // 実運用/本番コードでは前提に自信がない場合のみ有効化を検討。 + // validateListDigits(l1); + // validateListDigits(l2); + + let p: ListNode | null = l1; + let q: ListNode | null = l2; + let carry = 0; + + const dummy = new ListNode(0); + let tail = dummy; + + // p または q が残っている、もしくは carry が残っている間ループ + while (p !== null || q !== null || carry !== 0) { + const x = p !== null ? p.val : 0; + const y = q !== null ? q.val : 0; + + // 前提破り検出(任意) + // if (x < 0 || x > 9 || y < 0 || y > 9) { + // throw new RangeError('Digit out of [0,9] detected.'); + // } + + const sum = x + y + carry; + carry = Math.floor(sum / 10); + const digit = sum % 10; + + tail.next = new ListNode(digit); + tail = tail.next; + + if (p !== null) p = p.next; + if (q !== null) q = q.next; + } + + return dummy.next; +} + +// --- 任意の補助(無効化しても主処理に影響なし) --- +/** 値域チェック(0–9)— 本来は不要だが堅牢性重視で用意 */ +function validateListDigits(head: ListNode | null): void { + let node = head; + while (node !== null) { + const v = node.val; + // 整数判定(TS では number は浮動小数なので整数性は明示チェック) + if (!Number.isInteger(v) || v < 0 || v > 9) { + throw new RangeError("List contains a digit outside [0,9]."); + } + node = node.next; + } +} +// ``` + +// --- + +// ## 5. 制約条件 + +// * **外部ライブラリ禁止**(Node.js 標準のみ) +// * **メモリ使用:** 出力リスト生成以外は定数オーバヘッドのみ +// * **TypeScript strict mode:** 推奨(`"strict": true`)。未定義動作を抑制 + +// --- + +// ## TypeScript固有の最適化観点 + +// ### 型安全性の活用 + +// * **ListNode の構造を固定**し、`null` 合流は三項で即時数値化(`0`)することで分岐局所化。 +// * **整数性チェック(任意)**を用意し、前提破りの入力を明示的に `RangeError` にマップ可能。 + +// ### コンパイル時最適化 + +// * **プロパティ形状固定**(`val` → `next` の順で初期化)によりヒドゥンクラスの安定化を支援。 +// * **`Math.floor` を使用**し、ビット演算による 32bit 化副作用を避けて数値型を安定。 + +// ### 開発効率と保守性 + +// * LeetCode 互換の **最小 API**(`addTwoNumbers`)と **番兵ノード定石**で読みやすく、バグ温床の特例(先頭ノード分岐)を排除。 +// * 検証ロジックは **任意(コメントアウトで切替)** とし、競プロと業務の両要求に対応。 diff --git a/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/README.md b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/README.md new file mode 100644 index 00000000..d1b5c256 --- /dev/null +++ b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/GPT/README.md @@ -0,0 +1,238 @@ +# Add Two Numbers - 桁ごとの同時走査で O(n) 合成 + +## Table of Contents + +- [概要](#overview) +- [アルゴリズム要点(TL;DR)](#tldr) +- [図解](#figures) +- [正しさのスケッチ](#correctness) +- [計算量](#complexity) +- [Python 実装](#impl) +- [CPython 最適化ポイント](#cpython) +- [エッジケースと検証観点](#edgecases) +- [FAQ](#faq) + +--- + +

概要

+ +- **問題**: 2 つの非空単方向連結リスト `l1`, `l2` は、**各ノードが 1 桁の非負整数**を持ち、**逆順**に並ぶ数を表す。これらの**和**を同じ形式の連結リストで返す。 +- **制約**: 各ノード `0 ≤ val ≤ 9`、先頭の不要な 0 は存在しない(数 0 自体を除く)。リスト長は最大 100 程度。 +- **目標**: 1 パスで走査し、**繰り上がり**を管理して結果リストを構築する。安定した実装で**可読性・保守性・型安全性**を担保。 + +--- + +

アルゴリズム要点(TL;DR)

+ +- **戦略**: `l1` と `l2` を**同時走査**し、各桁の和 `x + y + carry` を計算。`digit = sum % 10`, `carry = sum // 10`。 +- **データ構造**: **番兵(ダミー)ノード+テールポインタ**で末尾追加を O(1) に簡素化。 +- **イミュータブル方針**: 入力は**非破壊**、新しいリストを生成。 +- **計算量**: **Time O(n)**(n は長い方の長さ)、**Space O(1)**(出力ノード除く)。 +- **実装の肝**: `while p or q or carry:` で**終了条件を一元化**し、先頭分岐や末尾処理の特例を排除。 + +--- + +

図解

+ +## **フローチャート(処理の流れ)** + +```mermaid +flowchart TD + Start[Start addTwoNumbers] --> Init[Init dummy tail carry] + Init --> LoopCheck{p or q or carry} + LoopCheck -- No --> Done[Return dummy.next] + LoopCheck -- Yes --> Read[Read x from p or 0 and y from q or 0] + Read --> Add[sum = x + y + carry] + Add --> Split[digit = sum % 10 and carry = sum // 10] + Split --> Append[append digit node to tail] + Append --> Advance[advance p and q if not null] + Advance --> LoopCheck +``` + +_説明_: `p` と `q` を順に進め、各ステップで `digit` を生成して末尾に接続する。`carry` が 0 になるまで継続。 + +## **データフロー(入力から出力まで)** + +```mermaid +graph LR + In1[l1] --> R1[read digits] + In2[l2] --> R1 + R1 --> S1[sum with carry] + S1 --> S2[split to digit and new carry] + S2 --> B1[build result list] + B1 --> Out1[result list] +``` + +_説明_: 各ステップで桁の和を算出し、`digit` と `carry` を更新しながら結果リストを構築。 + +--- + +

正しさのスケッチ

+ +- **不変条件** + + - ループ各反復後、`dummy.next` から `tail` までのリストは**下位桁から現在処理済みの桁まで**の正しい和を表す。 + - `carry` は常に 0〜1(`0 ≤ carry ≤ 1`)であり、未処理の繰り上がりを正しく保持。 + +- **網羅性** + + - `while p or q or carry` により、どちらかのリストが長い場合や**最終繰り上がり**が残る場合も確実に処理。 + +- **基底条件** + + - 両リストが空で `carry = 0` のときに終了し、結果を返す。 + +- **終了性** + + - 各反復で `p` または `q` が先に進むか `carry` が 0 に近づくため、**高々 max(len(l1), len(l2)) + 1** 反復で停止。 + +--- + +

計算量

+ +- **時間計算量**: **O(n)**(`n = max(len(l1), len(l2))`) +- **空間計算量**: **O(1)**(出力ノードを除く追加メモリは定数) + +| 観点 | Pure 生成(本実装) | 破壊的結合 | +| ---------- | ------------------- | -------------------------- | +| 入力破壊 | なし | あり得る | +| 実装複雑性 | 低 | 中(先頭分岐・所有権) | +| メモリ | 新規ノード数 = 桁数 | 一部節約可能だが保守性低下 | + +--- + +

Python 実装

+ +> **要件**: CPython 3.11+、LeetCode の class 形式、pylance でエラーが出ない型注釈。 +> **方針**: 入力は非破壊、番兵+テールで O(1) 連結、`Optional` を明示して null 安全。 + +```python +from __future__ import annotations +from typing import Optional, TYPE_CHECKING + +# LeetCode 環境では ListNode が事前定義されるが、 +# 単独ファイルでも型解決できるよう軽量フォールバックを用意する。 +if TYPE_CHECKING: + # 型チェック用スタブ(実行時には評価されない) + class ListNode: + val: int + next: Optional["ListNode"] +else: + try: + ListNode # type: ignore[name-defined] + except NameError: + class ListNode: # minimal runtime fallback + __slots__ = ("val", "next") + def __init__(self, val: int = 0, next: Optional["ListNode"] = None) -> None: + self.val = val + self.next = next + + +class Solution: + """ + Add Two Numbers + - 2 つの逆順連結リストが表す非負整数の和を、逆順連結リストで返す。 + - 入力は非破壊、番兵+テールで O(1) 連結。 + - Time O(n), Space O(1)(出力ノードを除く) + """ + + def addTwoNumbers( + self, + l1: Optional[ListNode], + l2: Optional[ListNode], + ) -> Optional[ListNode]: + """ + Args: + l1: 第1引数のリスト先頭(None の可能性は型上許容) + l2: 第2引数のリスト先頭(同上) + + Returns: + 和を表す新しい連結リストの先頭ノード + + Complexity: + Time: O(n) + Space: O(1) ※出力ノードを除く + """ + # 番兵(ダミー)ノードで先頭分岐を排除 + dummy: ListNode = ListNode(0) + tail: ListNode = dummy + + p: Optional[ListNode] = l1 + q: Optional[ListNode] = l2 + carry: int = 0 + + # どちらかにノードが残るか、繰り上がりが残る限り継続 + while p is not None or q is not None or carry != 0: + x: int = p.val if p is not None else 0 + y: int = q.val if q is not None else 0 + + total: int = x + y + carry + carry = total // 10 + digit: int = total % 10 + + # 生成ノードは毎回1個のみ + tail.next = ListNode(digit) + # tail.next は None ではないため、type: ignore を避ける代入形にする + tail = tail.next # type: ignore[assignment] + + # ポインタ前進 + if p is not None: + p = p.next + if q is not None: + q = q.next + + return dummy.next +``` + +## **主要コメント** + +- **基底条件**: `p`, `q` が尽き、`carry = 0` の時に終了。 +- **枝刈り**: 不要な分岐を番兵で削減。 +- **不変条件**: `dummy.next` から `tail` までが常に「これまでの正しい下位桁の和」を保持。 +- **終了処理**: 余り `carry` が 1 の場合もループで 1 桁を付加して完結。 + +--- + +

CPython最適化ポイント

+ +- **属性アクセス削減**: `p.val`, `q.val` は 1 回だけ読み取り、ローカル変数 `x`, `y` に束縛。 +- **関数呼び出し最小化**: `divmod` ではなく `//` と `%` を用い、意図を明確化。 +- **オブジェクト生成の抑制**: 毎反復で新ノード 1 個のみ。補助リスト・タプルを作らない。 +- **分岐一元化**: `while p or q or carry` により終了条件と処理本体を簡潔化。 +- **`__slots__`**(フォールバック): 低オーバーヘッドの簡易 ListNode でメモリ節約。 + +--- + +

エッジケースと検証観点

+ +- **両方ゼロ**: `l1=[0], l2=[0] → [0]` +- **繰り上がりのみで桁数増加**: `l1=[9,9,9], l2=[1] → [0,0,0,1]` +- **長さ不一致**: `l1` と `l2` の長さが大きく異なる(短い側は 0 として扱う) +- **片方が単一ノード**: `l1=[0], l2=[9,9]` など +- **最大長付近**: 100 桁同士でも 1 パスで安全に処理 +- **入力の前提破り**(理論上): `val` が 0–9 外や負値(LeetCode では発生しない) + +--- + +

FAQ

+ +- **Q. 破壊的に再利用すればメモリが減りますか?** + A. 一部削減は可能ですが、**先頭分岐や所有権の複雑化**で保守性が低下します。本問題では**非破壊生成が最適**です。 + +- **Q. 文字列や配列にしてから加算しては?** + A. 追加メモリと変換コストが増え、桁数が大きい場合に不利です。連結リストを**直接 1 パス**で処理するのが最短です。 + +- **Q. `divmod` を使う方が Pythonic?** + A. 可読性は高いですが、ここでは `//` と `%` で意図を明示しました。いずれでも正解です。 + +--- + +## **メタ情報** + +- **プラットフォーム/ID**: LeetCode 2 +- **関数シグネチャ** + `class Solution: def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:` +- **想定データ構造**: `ListNode`(単方向連結リスト) +- **ターゲット計算量**: **Time O(n)** / **Space O(1)**(出力除く) + +本 README は単一ファイルで完結し、図は Mermaid の厳格な記法要件に準拠しています。 diff --git a/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/README.md b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/README.md index f9f2acaf..193e7f20 100644 --- a/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/README.md +++ b/DataStructures/LinkedLists/leetcode/2. Add Two Numbers/README.md @@ -1,46 +1,44 @@ -TypeScript を使った「2. Add Two Numbers」問題の解答。 -リストは逆順で数字が格納されているため、対応するノードの数字を順に足し合わせ、繰り上がりも考慮。 - ---- - -### ✅ 解法:TypeScript 実装 +# ✅ 解法:TypeScript 実装 ```ts class ListNode { - val: number; - next: ListNode | null; + val: number; + next: ListNode | null; - constructor(val?: number, next?: ListNode | null) { - this.val = val ?? 0; - this.next = next ?? null; - } + constructor(val?: number, next?: ListNode | null) { + this.val = val ?? 0; + this.next = next ?? null; + } } -function addTwoNumbers(l1: ListNode | null, l2: ListNode | null): ListNode | null { - const dummyHead = new ListNode(0); - let current = dummyHead; - let carry = 0; +function addTwoNumbers( + l1: ListNode | null, + l2: ListNode | null +): ListNode | null { + const dummyHead = new ListNode(0); + let current = dummyHead; + let carry = 0; - while (l1 !== null || l2 !== null || carry > 0) { - const val1 = l1?.val ?? 0; - const val2 = l2?.val ?? 0; - const sum = val1 + val2 + carry; + while (l1 !== null || l2 !== null || carry > 0) { + const val1 = l1?.val ?? 0; + const val2 = l2?.val ?? 0; + const sum = val1 + val2 + carry; - carry = Math.floor(sum / 10); - current.next = new ListNode(sum % 10); - current = current.next; + carry = Math.floor(sum / 10); + current.next = new ListNode(sum % 10); + current = current.next; - l1 = l1?.next ?? null; - l2 = l2?.next ?? null; - } + l1 = l1?.next ?? null; + l2 = l2?.next ?? null; + } - return dummyHead.next; + return dummyHead.next; } ``` --- -### 🧠 アルゴリズムの解説 +## 🧠 アルゴリズムの解説 ```text 342 (l1 = [2, 4, 3]) @@ -56,34 +54,34 @@ function addTwoNumbers(l1: ListNode | null, l2: ListNode | null): ListNode | nul --- -### 🧪 テスト用ユーティリティ関数(任意) +## 🧪 テスト用ユーティリティ関数(任意) 以下は、配列と `ListNode` 間の変換を行う補助関数です: ```ts function arrayToList(arr: number[]): ListNode | null { - const dummy = new ListNode(); - let current = dummy; - for (const num of arr) { - current.next = new ListNode(num); - current = current.next; - } - return dummy.next; + const dummy = new ListNode(); + let current = dummy; + for (const num of arr) { + current.next = new ListNode(num); + current = current.next; + } + return dummy.next; } function listToArray(head: ListNode | null): number[] { - const result: number[] = []; - while (head !== null) { - result.push(head.val); - head = head.next; - } - return result; + const result: number[] = []; + while (head !== null) { + result.push(head.val); + head = head.next; + } + return result; } ``` --- -### 🧪 使用例 +## 🧪 使用例 ```ts const l1 = arrayToList([2, 4, 3]); @@ -99,38 +97,42 @@ console.log(listToArray(result)); // [7, 0, 8] --- -### ✅ 解決方法 +## ✅ 解決方法 -**方法①:既存の `ListNode` を使う** +**方法 ①:既存の `ListNode` を使う** -もし問題が与える `ListNode` クラスが既に存在している場合は、**再定義せずにそのまま使う**だけでOKです。以下のように `ListNode` の定義部分を削除して、`addTwoNumbers` の関数部分だけ使ってください。 +もし問題が与える `ListNode` クラスが既に存在している場合は、**再定義せずにそのまま使う**だけで OK です。 +以下のように `ListNode` の定義部分を削除して、`addTwoNumbers` の関数部分だけ使ってください。 ```ts -function addTwoNumbers(l1: ListNode | null, l2: ListNode | null): ListNode | null { - const dummyHead = new ListNode(0); - let current = dummyHead; - let carry = 0; - - while (l1 !== null || l2 !== null || carry > 0) { - const val1 = l1?.val ?? 0; - const val2 = l2?.val ?? 0; - const sum = val1 + val2 + carry; - - carry = Math.floor(sum / 10); - current.next = new ListNode(sum % 10); - current = current.next; - - l1 = l1?.next ?? null; - l2 = l2?.next ?? null; - } - - return dummyHead.next; +function addTwoNumbers( + l1: ListNode | null, + l2: ListNode | null +): ListNode | null { + const dummyHead = new ListNode(0); + let current = dummyHead; + let carry = 0; + + while (l1 !== null || l2 !== null || carry > 0) { + const val1 = l1?.val ?? 0; + const val2 = l2?.val ?? 0; + const sum = val1 + val2 + carry; + + carry = Math.floor(sum / 10); + current.next = new ListNode(sum % 10); + current = current.next; + + l1 = l1?.next ?? null; + l2 = l2?.next ?? null; + } + + return dummyHead.next; } ``` --- -**方法②:自分でテスト用に `ListNode` を書く場合** +**方法 ②:自分でテスト用に `ListNode` を書く場合** あなたがローカル環境などで単体テストをしたいなら、**他の `ListNode` 定義と競合しないように名前を変更するか、既存の定義を削除**してください。 @@ -138,27 +140,49 @@ function addTwoNumbers(l1: ListNode | null, l2: ListNode | null): ListNode | nul ```ts class MyListNode { - val: number; - next: MyListNode | null; + val: number; + next: MyListNode | null; - constructor(val?: number, next?: MyListNode | null) { - this.val = val ?? 0; - this.next = next ?? null; - } + constructor(val?: number, next?: MyListNode | null) { + this.val = val ?? 0; + this.next = next ?? null; + } } ``` --- -### 🔁 よくあるミス +## 🔁 よくあるミス -* LeetCode などのオンラインジャッジでは、`ListNode` が**あらかじめ定義済み**。 -* その環境で自分で `ListNode` を定義すると **TS2300: Duplicate identifier** が出る。 +- LeetCode などのオンラインジャッジでは、`ListNode` が**あらかじめ定義済み**。 +- その環境で自分で `ListNode` を定義すると **TS2300: Duplicate identifier** が出る。 --- -### 🎯 結論 +## 🎯 結論 + +- **LeetCode やオンライン環境** → `ListNode` の定義は削除してください。 +- **ローカルで実行** → 他に `ListNode` があれば削除、またはクラス名を変更してください。 -* **LeetCode やオンライン環境** → `ListNode` の定義は削除してください。 -* **ローカルで実行** → 他に `ListNode` があれば削除、またはクラス名を変更してください。 +こんにちは。AI コードアシスタントです。 + +TypeScript で `Duplicate identifier 'ListNode'` エラーが発生している問題を解決します。このエラーは、複数のファイルで同じ名前の `ListNode` クラスがグローバルスコープに定義されているために発生します。 + +この問題を解決するには、各ファイルをモジュールとして扱い、クラス定義がそのファイル内にスコープされるようにします。 +ファイルに `export` または `import` 文を追加することで、TypeScript はそのファイルをモジュールとして認識します。 + +以下のように、`AddTwoNumbers.ts` ファイルの先頭に `export {};` を追加してください。これにより、ファイルがモジュールとして扱われ、`ListNode` クラスのスコープがこのファイル内に限定されるため、他のファイルとの衝突が解消されます。 + +## 変更内容 + +```typescript +export {}; + +class ListNode { + val: number; + next: ListNode | null; +// ... 既存のコード +``` +この変更により、`ListNode` クラスがグローバルスコープを汚染しなくなり、識別子の重複エラーが解決されます。 +他のファイルでも同様のエラーが発生している場合は、同じように `export {};` を追加することで解決できます。 From 55f26e08eca959c279f45422462b51edabb761bb Mon Sep 17 00:00:00 2001 From: myoshizumi Date: Fri, 3 Oct 2025 18:43:11 +0900 Subject: [PATCH 2/2] leetcode 177. Nth Highest Salary SQL --- .../gpt/NthHighestSalary_mysql.md | 359 ++++++++++ .../gpt/NthHighestSalary_pandas.md | 254 +++++++ .../gpt/NthHighestSalary_postgres.md | 625 ++++++++++++++++++ 3 files changed, 1238 insertions(+) create mode 100644 SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_mysql.md create mode 100644 SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_pandas.md create mode 100644 SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_postgres.md diff --git a/SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_mysql.md b/SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_mysql.md new file mode 100644 index 00000000..be9a0fc4 --- /dev/null +++ b/SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_mysql.md @@ -0,0 +1,359 @@ +# ✅ 解法(MySQL 8+, LeetCode 互換関数) + +最少・最速の王道は「重複を除いた給与を降順にし、`OFFSET N-1` を 1 件だけ取得」。該当が無い場合、**スカラサブクエリは `NULL` を返す**ので、そのまま要件を満たします。 + +```sql +-- LeetCodeのデータベース上でそのまま通る想定 +CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT +BEGIN + RETURN ( + SELECT DISTINCT salary + FROM Employee + ORDER BY salary DESC + LIMIT 1 OFFSET N - 1 + ); +END; +``` + +## ローカル(MySQL CLI)で関数を作る場合の書式 + +```sql +DELIMITER $$ + +CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT +DETERMINISTIC +READS SQL DATA +BEGIN + RETURN ( + SELECT DISTINCT salary + FROM Employee + ORDER BY salary DESC + LIMIT 1 OFFSET N - 1 + ); +END$$ + +DELIMITER ; +``` + +### 単発クエリ版(関数を作らず結果だけ見たい時) + +```sql +-- 例: N=2 のとき +SELECT ( + SELECT DISTINCT salary + FROM Employee + ORDER BY salary DESC + LIMIT 1 OFFSET 2 - 1 +) AS `getNthHighestSalary(2)`; +``` + +--- + +## 🧠 アルゴリズムの考え方 + +1. `DISTINCT salary` で**重複を排除** +2. `ORDER BY salary DESC` で**高い順に並べ替え** +3. `OFFSET N-1` までスキップし、`LIMIT 1` を**1 件だけ取り出す** +4. 件数不足なら**空集合**→ スカラサブクエリは **`NULL`** を返す + +**計算量**: + +- D = distinct な給与の個数 +- 並べ替えが支配的 → **O(D log D)** + +--- + +## 🧩 図解 1:全体フロー + +```mermaid +flowchart LR + A[Employee table] --> B[Select DISTINCT salary] + B --> C[ORDER BY salary DESC] + C --> D[OFFSET N-1] + D --> E[LIMIT 1] + E --> F[Return salary or NULL] +``` + +--- + +## 🔎 図解 2:Example 1 の流れ(N=2) + +入力: + +```text +id | salary +1 | 100 +2 | 200 +3 | 300 +``` + +```mermaid +flowchart LR + S1[Input salaries: 100,200,300; N=2] --> D1[Distinct: 100,200,300] + D1 --> O1[Order DESC: 300,200,100] + O1 --> K1[Offset 1] + K1 --> T1[Take 1: 200] + T1 --> R1[Result: 200] +``` + +--- + +## 🟦 図解 3:Example 2 の流れ(N=2, データ不足) + +入力: + +```text +id | salary +1 | 100 +``` + +```mermaid +flowchart LR + S2[Input salaries: 100; N=2] --> D2[Distinct: 100] + D2 --> O2[Order DESC: 100] + O2 --> K2[Offset 1] + K2 --> T2[Take 1: none] + T2 --> R2[Result: NULL] +``` + +--- + +## 🧭 代替解(参考):ウィンドウ関数 `DENSE_RANK` + +MySQL 8+ なら `DENSE_RANK()` でも書けます(**重複を自然にまとめる**)。 +ただし関数の `RETURN` に載せるには**スカラ化**が必要なので、`LIMIT 1` などで 1 行に絞ります。 + +```sql +-- DISTINCT→DENSE_RANK で N 位の給与を抽出 +CREATE FUNCTION getNthHighestSalary_v2(N INT) RETURNS INT +BEGIN + RETURN ( + SELECT salary + FROM ( + SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rk + FROM (SELECT DISTINCT salary FROM Employee) AS d + ) AS r + WHERE rk = N + ORDER BY salary + LIMIT 1 + ); +END; +``` + +> どちらも要件を満たしますが、**簡潔で早いのは LIMIT/OFFSET 版**です。 + +--- + +## ✅ 動作確認クエリ + +```sql +-- サンプルデータ +TRUNCATE TABLE Employee; +INSERT INTO Employee(id, salary) VALUES (1,100),(2,200),(3,300); + +-- 期待: 200 +SELECT getNthHighestSalary(2) AS `getNthHighestSalary(2)`; + +-- データ不足パターン +TRUNCATE TABLE Employee; +INSERT INTO Employee(id, salary) VALUES (1,100); + +-- 期待: NULL +SELECT getNthHighestSalary(2) AS `getNthHighestSalary(2)`; +``` + +--- + +## 🛠️ Mermaid 図のパースエラー回避のコツ + +- ノード内テキストは**1 行・短文**に(`
` は使わない) +- 日本語や絵文字で崩れる場合は**英数字**に +- 記号は最小限(カンマやコロンは OK) + +--- + +エラー原因と対処、それから「LeetCode の SQL 実行環境のバージョン確認」についてまとめます。 + +--- + +## 1) そのエラーのよくある原因 + +**メッセージ**: `... near '- 1 ); END'` +主に次のどちらかが原因です。 + +1. **`DELIMITER` を変えずに `CREATE FUNCTION ...` を実行** + ローカルの MySQL では、関数本体内の `;` で文が分割されてしまいます。 + → `DELIMITER $$` を使ってから関数を作成してください(下記コード参照)。 + +2. **`LIMIT ... OFFSET N - 1` の「式」が許容されないケース** + 環境によっては `OFFSET` に**式**(`N - 1`)を直接書くと構文エラーになることがあります。 + → 先に `SET N = N - 1;` で**変数に代入**してから `OFFSET N` を使うと安定します。 + (LeetCode の解答投稿でもこの書き方がよく使われます。([leetcode.com][1])) + +### ✅ ローカル MySQL で確実に通る版 + +```sql +DELIMITER $$ + +CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT +DETERMINISTIC +READS SQL DATA +BEGIN + SET N = N - 1; -- ここで式を変数に畳み込む + RETURN ( + SELECT DISTINCT salary + FROM Employee + ORDER BY salary DESC + LIMIT 1 OFFSET N + ); +END$$ + +DELIMITER ; +``` + +> 代替:二引数の `LIMIT` でも OK(`LIMIT offset, row_count`) +> +> ```sql +> ... +> SET N = N - 1; +> RETURN ( +> SELECT DISTINCT salary +> FROM Employee +> ORDER BY salary DESC +> LIMIT N, 1 +> ); +> ... +> ``` + +--- + +## 2) LeetCode 側の「SQL バージョン」を確認するには? + +結論から言うと、**LeetCode は公式に RDB の「厳密なバージョン番号」を公開していません**。問題ページの「MySQL / MS SQL Server / Oracle」など**方言は選べますが**、細かなバージョンは明示されません。 +一方で、LeetCode の Discuss(公式トピック)や問題解説では **MySQL のウィンドウ関数(`DENSE_RANK()` など)を前提にした解法が案内**されており、 +**少なくとも MySQL 8+ 相当の機能が使える**ことが読み取れます。([leetcode.com][2]) + +### 現実的な確認手段 + +- **機能ベースで推測**する + 例)`DENSE_RANK()` が動く → **MySQL 8+ 機能**は使える、と判断。([leetcode.com][2]) +- **Discuss の公式ポストや解説をチェック** + LeetCode が配信している Database Primer などで使っている機能を確認。([leetcode.com][2]) +- **サンドボックスに依存しない書き方に寄せる** + 例えば今回の問題は、**`DISTINCT + ORDER BY DESC + LIMIT`** でウィンドウ関数を使わずに解けるため、**環境差の影響を最小化**できます(本回答の関数)。([leetcode.com][1]) + +> 備考:LeetCode の採点環境では任意クエリで `SELECT VERSION();` のような“自己診断”を走らせることは基本的にできません(採点は固定スキーマ/固定 IO で比較されるため)。 + +--- + +## 3) LeetCode 提出用(簡潔版) + +LeetCode のエディタにそのまま貼るなら、次の **「先に `N` を更新 → `OFFSET N`」** 版が堅いです。 + +```sql +CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT +BEGIN + SET N = N - 1; + RETURN ( + SELECT DISTINCT salary + FROM Employee + ORDER BY salary DESC + LIMIT 1 OFFSET N + ); +END; +``` + +--- + +## 4) それでもダメなときのチェックリスト + +- ローカル実行なら **`DELIMITER` 切り替え**を入れているか? +- `Employee` テーブル名・列名の **大小文字**やタイプミスがないか? +- 既に同名関数があれば **`DROP FUNCTION getNthHighestSalary;`** 実行後に再作成 +- それでも通らなければ、`SET N = N - 1;` を **`DECLARE off INT DEFAULT N-1;`** → `OFFSET off` に変えてみる(同趣旨) + +--- + +[1]: https://leetcode.com/problems/nth-highest-salary/discuss/498281/mysql-offset-by-n-1?utm_source=chatgpt.com "Nth Highest Salary - LeetCode" +[2]: https://leetcode.com/discuss/post/1600719/database-sql-primer-part-2-window-functi-sm8m/?utm_source=chatgpt.com "Database SQL Primer (Part 2) [ Window Functions ] - Discuss" + +原因はシンプルで、**`DELIMITER` は MySQL サーバーの構文じゃなく “クライアント側専用ディレクティブ”** だからです。 +あなたの環境(LeetCode や一部 GUI ツール)は `DELIMITER` を解釈せず、そのままサーバーに送ってしまうため、 +サーバーが **`DELIMITER $$` を SQL として解釈 → 構文エラー** になります。 + +以下から自分の環境に合う対処を選んでください。 + +--- + +## 1) LeetCode(最有力)なら:`DELIMITER` 行は**全部削除** + +LeetCode では **`DELIMITER` を書かない** で OK です。次をそのまま貼ってください。 + +```sql +CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT +BEGIN + -- OFFSET での式(N-1)が環境により嫌がられる場合があるので、先に畳み込みます + SET N = N - 1; + RETURN ( + SELECT DISTINCT salary + FROM Employee + ORDER BY salary DESC + -- どちらでもOK(好きな方で) + -- LIMIT 1 OFFSET N + LIMIT N, 1 + ); +END; +``` + +動作確認(LeetCode では提出後に内部で呼ばれますが、ローカルならこう呼べます): + +```sql +SELECT getNthHighestSalary(2); +``` + +--- + +## 2) MySQL CLI / MySQL Workbench なら:`DELIMITER` を**別行で**使う + +`mysql` CLI や Workbench は `DELIMITER` を理解します。**必ず “別行” で実行**してください(1 行目の頭から `DELIMITER` を置く)。 + +```sql +DELIMITER $$ + +CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT +DETERMINISTIC +READS SQL DATA +BEGIN + SET N = N - 1; + RETURN ( + SELECT DISTINCT salary + FROM Employee + ORDER BY salary DESC + LIMIT N, 1 + ); +END$$ + +DELIMITER ; +``` + +> ポイント +> +> - 「スクリプト全体を一気に実行」すれば OK(Workbench なら稲妻ボタン)。 +> - `DELIMITER` と `CREATE FUNCTION ... END$$`、`DELIMITER ;` は**各行独立**で送ること。 + +--- + +## 3) DataGrip / DBeaver / phpMyAdmin など GUI で `DELIMITER` を解釈しない場合 + +- **方法 A(推奨)**: そのツールの「バッチ/スクリプト実行モード」を使う(Workbench と同様に扱えるモードがあることが多い)。 +- **方法 B**: エディタ設定で「区切り文字(Delimiter)」を `$$` に変更できる UI があれば、そこで設定してから実行。 +- **方法 C**: 面倒なら **LeetCode 方式(`DELIMITER` 無し)**の関数を貼って実行(多くの GUI は 1 クエリとしてまとめて送るため、そのまま通ります)。 + +--- + +## まだエラーが出る場合のチェックリスト + +- 既に同名関数がある → `DROP FUNCTION getNthHighestSalary;` してから再作成 +- テーブル名・列名のタイプミス無し?(`Employee` / `salary`) +- クライアントがステートメントを**1 行ずつ**投げていない?(`CREATE FUNCTION` は**1 リクエストで全文**送る必要があります) +- 以前の環境では `OFFSET N-1` が NG だった → **二引数 `LIMIT N, 1`** を使う(上記スニペットは対応済み) diff --git a/SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_pandas.md b/SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_pandas.md new file mode 100644 index 00000000..caa054af --- /dev/null +++ b/SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_pandas.md @@ -0,0 +1,254 @@ +# pandas を使って **n 番目に高い “重複なし” 給与**を求める関数と、手順の図解(Mermaid) + +## ✅ 方針(pandas) + +1. `salary` 列を取り出す +2. `dropna()`(任意)で欠損除去 +3. `drop_duplicates()` で **Distinct** +4. `sort_values(ascending=False)` で **降順** +5. `n<=0` なら **None**、件数不足でも **None** +6. 十分ならインデックス `n-1` を返す + +--- + +## 🧩 実装(関数) + +```python +import pandas as pd +from typing import Optional + +def get_nth_highest_salary(df: pd.DataFrame, n: int) -> Optional[int]: + """ + Return the nth highest DISTINCT salary from Employee dataframe. + If n <= 0 or distinct count < n, return None. + + Parameters + ---------- + df : pd.DataFrame + Must contain a column named 'salary' (int). + n : int + 1-based rank. + + Returns + ------- + Optional[int] + nth highest distinct salary or None. + """ + if n <= 0: + return None + + distinct_desc = ( + df['salary'] + .dropna() + .drop_duplicates() + .sort_values(ascending=False) + ) + + if len(distinct_desc) < n: + return None + + # pandas では int(np.integer) になる可能性があるので int() で標準intへ + return int(distinct_desc.iloc[n - 1]) +``` + +### ワンライナー版(例外を出さずに None へフォールバック) + +```python +def get_nth_highest_salary_one_liner(df: pd.DataFrame, n: int) -> Optional[int]: + if n <= 0: + return None + s = df['salary'].dropna().drop_duplicates().sort_values(ascending=False) + return int(s.iloc[n - 1]) if len(s) >= n else None +``` + +--- + +## 🧪 サンプルテスト + +```python +# Example 1 +df1 = pd.DataFrame({"id":[1,2,3], "salary":[100,200,300]}) +print(get_nth_highest_salary(df1, 2)) # 200 + +# Example 2 (不足) +df2 = pd.DataFrame({"id":[1], "salary":[100]}) +print(get_nth_highest_salary(df2, 2)) # None + +# 追加: n<=0 は None +print(get_nth_highest_salary(df1, -1)) # None + +# 追加: 重複があってもDistinctで処理 +df3 = pd.DataFrame({"id":[1,2,3,4], "salary":[100,200,200,300]}) +print(get_nth_highest_salary(df3, 2)) # 200 +``` + +--- + +## 📈 計算量 + +- N = レコード数, D = distinct 給与数 +- `drop_duplicates()` が O(N)、`sort_values()`(D 件のソート)が **O(D log D)** +- トータルは **O(N + D log D)**(通常はソートが支配的) + +--- + +## 🧭 代替実装(`nlargest` を使う) + +`unique()` → `Series` 化 → `nlargest(n)` → `iloc[-1]`。 +※ 件数不足チェックは必要。 + +```python +import pandas as pd + +def get_nth_highest_salary_v2(df: pd.DataFrame, n: int) -> Optional[int]: + if n <= 0: + return None + uniq = pd.Series(df['salary'].dropna().unique()) + if len(uniq) < n: + return None + return int(uniq.nlargest(n).iloc[-1]) +``` + +--- + +## 🗺️ 図解(Mermaid) + +### 図 1: 全体フロー + +```mermaid +flowchart LR + A[Employee dataframe] --> B[Select salary] + B --> C[dropna] + C --> D[drop_duplicates] + D --> E[sort_values desc] + E --> F[check n<=0] + F -->|yes| G[return None] + F -->|no| H[check length>=n] + H -->|no| I[return None] + H -->|yes| J[iloc n-1] + J --> K[return value] +``` + +### 図 2: Example1 n=2 + +```mermaid +flowchart LR + S1[input 100,200,300 n=2] --> D1[distinct 100,200,300] + D1 --> O1[order desc 300,200,100] + O1 --> P1[pick index 1] + P1 --> R1[result 200] +``` + +### 図 3: Example2 n=2 insufficient + +```mermaid +flowchart LR + S2[input 100 n=2] --> D2[distinct 100] + D2 --> O2[order desc 100] + O2 --> L2[length 1 R2[result None] +``` + +### 図 4: n<=0 + +```mermaid +flowchart LR + S3[input any n<=0] --> C3[check n<=0] + C3 --> R3[result None] +``` + +--- + +必要なら、**Polars 版**や **SQL→pandas 変換パターン**も作れます。テスト用の DataFrame をこちらで生成して結果を表形式で出すことも可能です。 + +LeetCode の「関数を返す形式」に合わせて、**`pd.DataFrame` を返す関数**に整えます。 +仕様は「Employee テーブルを pandas DataFrame として受け取り、 +N 番目に高い distinct salary を `getNthHighestSalary(N)` というカラム名の DataFrame として返す」です。 + +--- + +## ✅ 実装 + +```python +import pandas as pd +from typing import Optional + +def nth_highest_salary(employee: pd.DataFrame, N: int) -> pd.DataFrame: + """ + Return the Nth highest distinct salary as a DataFrame with column name + 'getNthHighestSalary(N)'. If there are fewer than N distinct salaries or N <= 0, + return a DataFrame with None. + + Parameters + ---------- + employee : pd.DataFrame + DataFrame with at least a 'salary' column. + N : int + 1-based rank of salary to retrieve. + + Returns + ------- + pd.DataFrame + Single-row DataFrame with one column named 'getNthHighestSalary(N)'. + """ + colname = f"getNthHighestSalary({N})" + + if N <= 0: + return pd.DataFrame({colname: [None]}) + + distinct_desc = ( + employee["salary"] + .dropna() + .drop_duplicates() + .sort_values(ascending=False) + .reset_index(drop=True) + ) + + if len(distinct_desc) < N: + return pd.DataFrame({colname: [None]}) + else: + return pd.DataFrame({colname: [int(distinct_desc.iloc[N - 1])]}) + + +# ===== サンプル動作確認 ===== +if __name__ == "__main__": + df1 = pd.DataFrame({"id": [1, 2, 3], "salary": [100, 200, 300]}) + print(nth_highest_salary(df1, 2)) + # 期待: 200 + + df2 = pd.DataFrame({"id": [1], "salary": [100]}) + print(nth_highest_salary(df2, 2)) + # 期待: None + + df3 = pd.DataFrame({"id": [1, 2, 3, 4], "salary": [100, 200, 200, 300]}) + print(nth_highest_salary(df3, 2)) + # 期待: 200 +``` + +--- + +## 🧪 実行例 + +```text +>>> df1 + id salary +0 1 100 +1 2 200 +2 3 300 + +>>> nth_highest_salary(df1, 2) + getNthHighestSalary(2) +0 200 + +>>> nth_highest_salary(df1, 5) + getNthHighestSalary(5) +0 None + +>>> nth_highest_salary(df1, -1) + getNthHighestSalary(-1) +0 None +``` + +--- + +これで **LeetCode の PostgreSQL/MySQL 版と同じ入出力フォーマット**が pandas で再現できます。 diff --git a/SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_postgres.md b/SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_postgres.md new file mode 100644 index 00000000..8fb609f5 --- /dev/null +++ b/SQL/Leetcode/177. Nth Highest Salary/gpt/NthHighestSalary_postgres.md @@ -0,0 +1,625 @@ +# ✅ 解法(PostgreSQL) + +## 1) 最短シンプル解(SQL 関数) + +- 重複を除いた給与を降順に並べ、`OFFSET n-1` でスキップして `LIMIT 1` +- 結果が無ければ **SQL 関数は `NULL` を返す** ので要件を満たします +- PostgreSQL では **`DELIMITER` は不要**、`$$` のドルクオートで本体を囲みます + +```sql +CREATE OR REPLACE FUNCTION getNthHighestSalary(n integer) +RETURNS integer +LANGUAGE sql +STABLE +AS $$ + SELECT salary + FROM ( + SELECT DISTINCT salary + FROM employee + ORDER BY salary DESC + OFFSET GREATEST(n - 1, 0) -- 念のため負数ガード + LIMIT 1 + ) AS t; +$$; +``` + +### 使い方 + +```sql +SELECT getNthHighestSalary(2); +``` + +--- + +## 2) 単発クエリ版(関数を作らずに結果だけ見る) + +```sql +-- 例: n = 2 +SELECT ( + SELECT salary + FROM ( + SELECT DISTINCT salary + FROM employee + ORDER BY salary DESC + OFFSET 2 - 1 + LIMIT 1 + ) s +) AS "getNthHighestSalary(2)"; +``` + +--- + +## 3) 代替:ウィンドウ関数 DENSE_RANK 版(参考) + +`DENSE_RANK()` は**同額を同順位**にするので「重複を自然にまとめて n 位の給与」を取得できます。 +(重複を先に落とすため一段サブクエリを入れています) + +```sql +CREATE OR REPLACE FUNCTION getNthHighestSalary_v2(n integer) +RETURNS integer +LANGUAGE sql +STABLE +AS $$ + SELECT salary + FROM ( + SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rk + FROM (SELECT DISTINCT salary FROM employee) d + ) r + WHERE rk = n + LIMIT 1; +$$; +``` + +--- + +## 🧪 動作確認スニペット + +```sql +TRUNCATE TABLE employee; +INSERT INTO employee(id, salary) VALUES (1,100),(2,200),(3,300); + +-- 期待: 200 +SELECT getNthHighestSalary(2); + +TRUNCATE TABLE employee; +INSERT INTO employee(id, salary) VALUES (1,100); + +-- 期待: NULL +SELECT getNthHighestSalary(2); +``` + +--- + +## 🧠 アルゴリズムと計算量 + +- 手順: **DISTINCT** → **ORDER BY DESC** → **OFFSET n-1** → **LIMIT 1** +- 計算量: 重複除去とソートが支配的 → **O(D log D)**(D = distinct 給与数) + +--- + +## 📈 パフォーマンス Tips(任意) + +```sql +-- 並び替え用のBTREEインデックス(DESCで作るとORDER BY DESCに効きやすい) +CREATE INDEX IF NOT EXISTS idx_employee_salary_desc ON employee (salary DESC); +``` + +> 参考:`SELECT DISTINCT salary ORDER BY salary DESC` はクエリプランにより +> HashAggregate+Sort か、IndexScan で最適化されることがあります。 + +--- + +## 🧩 図解(Mermaid) + +## 図 1: 全体フロー + +```mermaid +flowchart LR + A[Employee] --> B[Distinct salary] + B --> C[Order by salary desc] + C --> D[Offset n-1] + D --> E[Limit 1] + E --> F[Return salary or NULL] +``` + +## 図 2: Example1 N=2 + +```mermaid +flowchart LR + S1[Input: 100,200,300; N=2] --> D1[Distinct: 100,200,300] + D1 --> O1[Order desc: 300,200,100] + O1 --> K1[Offset 1] + K1 --> T1[Take 1] + T1 --> R1[Result: 200] +``` + +## 図 3: Example2 N=2 Data 不足 + +```mermaid +flowchart LR + S2[Input: 100; N=2] --> D2[Distinct: 100] + D2 --> O2[Order desc: 100] + O2 --> K2[Offset 1] + K2 --> T2[Take 1: none] + T2 --> R2[Result: NULL] +``` + +> パースエラー回避のコツ:**英数字のみ/1 行テキスト/`
`不使用**を徹底 + +--- + +必要なら、**CTE で段階を可視化した版**や、**順位表を返すクエリ(上位 K 件)**も用意できます。さらに図解を細分化したい場合も言ってください。 + +なるほど 👍 +エラーメッセージは **PostgreSQL がその関数を見つけられなかった** という意味です。典型的には次のケースがあります: + +--- + +## ✅ エラーの原因と対策 + +1. **関数がまだ作られていない** + + - `CREATE FUNCTION` を実行していない + - 別セッション/別データベースに作成した + + → 必ず **同じ DB/同じスキーマ** で関数を作ってから呼び出してください。 + + ```sql + CREATE OR REPLACE FUNCTION getnthhighestsalary(n integer) + RETURNS integer + LANGUAGE sql + STABLE + AS $$ + SELECT salary + FROM ( + SELECT DISTINCT salary + FROM employee + ORDER BY salary DESC + OFFSET GREATEST(n-1,0) + LIMIT 1 + ) t; + $$; + ``` + +2. **名前の大小文字が違う** + + - PostgreSQL は **デフォルトで小文字化**します + `CREATE FUNCTION getNthHighestSalary(...)` → 実際には `getnthhighestsalary` として登録されます。 + - 呼び出し時に `"getNthHighestSalary"(2)` と **ダブルクオート付き**で書けば大文字も維持できますが、普通は小文字で統一します。 + + → なので呼び出しは **小文字**でこうしてください: + + ```sql + SELECT getnthhighestsalary(2); + ``` + +3. **型が合わない** + + - 定義は `integer` なのに、`'2'` (文字列)を渡していると合いません。 + - 数値リテラルで呼び出してください: + + ```sql + SELECT getnthhighestsalary(2); + ``` + + - もし `bigint` で定義したのなら、引数も `2::bigint` のようにキャスト。 + +--- + +## 🛠️ 動作確認の手順(おすすめフロー) + +```sql +-- 1. 関数を確認 +\df+ getnthhighestsalary + +-- 2. 関数が無ければ作成 +CREATE OR REPLACE FUNCTION getnthhighestsalary(n integer) +RETURNS integer +LANGUAGE sql +AS $$ + SELECT salary + FROM ( + SELECT DISTINCT salary + FROM employee + ORDER BY salary DESC + OFFSET GREATEST(n-1,0) + LIMIT 1 + ) t; +$$; + +-- 3. 呼び出し(小文字で) +SELECT getnthhighestsalary(2); +``` + +--- + +👉 まとめると: + +- **関数定義をまず実行**(同じ DB に) +- **関数名は小文字で呼び出す**(`getnthhighestsalary`) +- **整数リテラルで渡す** + +--- + +ご質問です: +PostgreSQL は **ローカル環境**(psql/pgAdmin など)で試していますか?それとも **LeetCode の PostgreSQL 環境**で提出していますか? +どちらかによって、関数の作成方法や確認コマンドをもう少し調整できますよ。 +あっ、それは自然ですね 🙂 + +`\df+` は **psql クライアント専用のメタコマンド** なので、普通の SQL としてサーバーに送るとエラーになります。 + +--- + +## 🔑 ポイント + +- `\df` / `\dt` / `\l` など **バックスラッシュで始まるコマンドは psql 専用** +- pgAdmin や LeetCode のオンラインエディタなどでは **使えません** +- サーバーに送る「正しい SQL」は `SELECT ... FROM pg_catalog.pg_proc ...` のような形 + +--- + +## ✅ PostgreSQL で関数を確認する正しい方法(SQL) + +```sql +SELECT proname, proargtypes::regtype[], prorettype::regtype +FROM pg_proc +WHERE proname = 'getnthhighestsalary'; +``` + +これで、その名前の関数が登録されているか確認できます。 + +--- + +## ✅ 改めて安全な定義&実行フロー + +1. 関数を作成 + + ```sql + CREATE OR REPLACE FUNCTION getnthhighestsalary(n integer) + RETURNS integer + LANGUAGE sql + STABLE + AS $$ + SELECT salary + FROM ( + SELECT DISTINCT salary + FROM employee + ORDER BY salary DESC + OFFSET GREATEST(n-1,0) + LIMIT 1 + ) t; + $$; + ``` + +2. 呼び出す(必ず小文字で) + +```sql +SELECT getnthhighestsalary(2); +``` + +--- + +## 💡 まとめ + +- `\df+` は **psql 専用コマンド** → GUI やオンライン環境では使えない +- 関数確認は **SQL で pg_proc を SELECT** +- 呼び出すときは **小文字名**にそろえる (`getnthhighestsalary`) + +--- + +👉 ご質問ですが、今試しているのは **ローカルの psql** ですか? それとも **pgAdmin** / **LeetCode SQL 環境**ですか? +それによって「関数確認のベストな方法」が変わります。 +なるほど、このエラーは PostgreSQL で典型的に起きるやつです。ポイントは **関数名の大文字小文字の扱い** です。 + +--- + +## 🔑 PostgreSQL のルール + +- `CREATE FUNCTION getNthHighestSalary(...)` と書いても、実際には **小文字に変換されて `getnthhighestsalary` として登録** されます。 +- 呼び出し側で `SELECT NthHighestSalary(2);` のように大文字を混ぜると、PostgreSQL は **「ダブルクオート付きで完全一致」** を探しますが、そんな名前の関数は存在しないのでエラーになります。 + +--- + +## ✅ 解決方法 + +### 方法 1: 小文字で呼び出す + +関数定義をそのままにして、呼び出しを小文字に揃える。 + +```sql +-- 定義 +CREATE OR REPLACE FUNCTION getnthhighestsalary(n integer) +RETURNS integer +LANGUAGE sql +STABLE +AS $$ + SELECT salary + FROM ( + SELECT DISTINCT salary + FROM employee + ORDER BY salary DESC + OFFSET GREATEST(n-1,0) + LIMIT 1 + ) t; +$$; + +-- 呼び出し(小文字) +SELECT getnthhighestsalary(2); +``` + +### 方法 2: 大文字小文字を保持して呼び出したい場合 + +ダブルクオートで囲んで定義&呼び出しをする。ただし一般的には非推奨です。 + +```sql +CREATE OR REPLACE FUNCTION "NthHighestSalary"(n integer) +RETURNS integer +LANGUAGE sql +AS $$ + SELECT salary + FROM ( + SELECT DISTINCT salary + FROM employee + ORDER BY salary DESC + OFFSET GREATEST(n-1,0) + LIMIT 1 + ) t; +$$; + +-- 呼び出すときもダブルクオート必須 +SELECT "NthHighestSalary"(2); +``` + +--- + +## 🧪 チェックリスト + +1. 関数を作成した DB に接続しているか? +2. `employee` テーブルが存在しているか? +3. 呼び出すときに **小文字で統一しているか?** + +--- + +👉 まとめると、LeetCode/PostgreSQL で通したいなら **`SELECT getnthhighestsalary(2);`** のように小文字で呼べば OK です。 + +--- + +ご確認ですが、あなたは **LeetCode の PostgreSQL 環境**でこの問題を解いていますか? それとも **ローカルの PostgreSQL** でしょうか? それに応じて「一番確実な定義&呼び出し方」を示しますよ。 + +OK、LeetCode の **PostgreSQL** 実行環境で確実に通る形を“そのまま貼れる”ようにまとめました。 +(LeetCode は `DELIMITER` を使いません。PostgreSQL なので **`plpgsql` か `sql` 関数**で書きます。) + +--- + +## 手順(LeetCode/PostgreSQL) + +1. 問題ページの **DB 方言を「PostgreSQL」** に切り替える +2. 下のスニペットを **そのまま実行** + + - 小文字関数 `getnthhighestsalary` を本体にし、 + 互換用に **CamelCase の別名** `"getNthHighestSalary"` も作っています(どちらで呼ばれても OK)。 + +```sql +-- 本体(小文字名): LeetCode側が小文字として解決できるようにしておく +CREATE OR REPLACE FUNCTION getnthhighestsalary(n INT) +RETURNS INT +LANGUAGE plpgsql +STABLE +AS $$ +DECLARE + res INT; +BEGIN + SELECT salary INTO res + FROM ( + SELECT DISTINCT salary + FROM employee -- ← LeetCodeのPostgreSQLは基本的に小文字テーブル名 + ORDER BY salary DESC + OFFSET GREATEST(n - 1, 0) + LIMIT 1 + ) t; + + RETURN res; -- 見つからなければNULLが返る +END; +$$; + +-- 互換用エイリアス(CamelCase名): ランナーがこちら名で呼んでも動く +CREATE OR REPLACE FUNCTION "getNthHighestSalary"(n INT) +RETURNS INT +LANGUAGE plpgsql +STABLE +AS $$ +BEGIN + RETURN getnthhighestsalary(n); +END; +$$; +``` + +> 補足 +> +> - **関数名の大文字小文字**:PostgreSQL は未クオート識別子を**小文字化**します。 +> そのため、`SELECT NthHighestSalary(2);` のように CamelCase で呼ばれると「存在しない」になりやすい問題があります。上のように **両方用意**しておくと安全です。 +> - **テーブル名**:`employee` を**小文字**で書いています(LeetCode の Postgres 環境では通常 unquoted =小文字化された実体名です)。 + +--- + +## すぐ試す動作確認(任意) + +```sql +-- (任意)簡易確認。LeetCodeでは提出時に自動実行されますが、手元確認用 +-- SELECT getnthhighestsalary(2); +-- SELECT "getNthHighestSalary"(2); +``` + +--- + +## まだ失敗する場合のチェック + +- 実行環境が **MySQL ではなく PostgreSQL** になっているか +- 先に別の同名関数が入っていて衝突していないか(`CREATE OR REPLACE` で上書きできます) +- テーブル名が `employee` で存在するか(スキーマはデフォルトの `public` 想定) +- ランナーが CamelCase で呼んでも大丈夫なように **両関数**が作られているか + +これで `function nthhighestsalary(integer) does not exist` の実行時エラーは解消できるはずです。 +必要なら **純 SQL 関数(`LANGUAGE sql`)版**や、**`DENSE_RANK()` を使う別解**も出します。 + +原因はほぼ確実に **LeetCode 側が `SELECT NthHighestSalary(…);` を呼んでいるのに、あなたが作った関数名が一致していない**(または大文字小文字/スキーマの差異)ことです。 +PostgreSQL は未クオート識別子を**すべて小文字に折りたたむ**ため、`NthHighestSalary` と書いても実際は `nthhighestsalary` を探します。 +テストログにも `function nthhighestsalary(integer) does not exist` と出ています。 + +以下の手順で“どの呼び名でもヒットする”ように直してください(LeetCode の PostgreSQL でそのまま実行可)。 + +--- + +## 修正版(両方の関数名を用意:小文字版+ CamelCase 版) + +```sql +-- 衝突を避けて作り直す(存在していれば上書き) +CREATE OR REPLACE FUNCTION nthhighestsalary(n INT) +RETURNS INT +LANGUAGE sql +STABLE +AS $$ + SELECT salary + FROM ( + SELECT DISTINCT salary + FROM employee -- ← LeetCodeでは実体は小文字のことが多い + ORDER BY salary DESC + OFFSET GREATEST(n - 1, 0) -- n<=0の保険 + LIMIT 1 + ) t; +$$; + +-- LeetCodeが CamelCase で呼んでも通るようにラッパーを作る +CREATE OR REPLACE FUNCTION "NthHighestSalary"(n INT) +RETURNS INT +LANGUAGE sql +STABLE +AS $$ + SELECT nthhighestsalary(n); +$$; +``` + +> ポイント +> +> - **関数名**:テスターが `NthHighestSalary` と呼んでも、上のラッパーが受けます。 +> - **テーブル名**:`employee` を未クオート小文字で参照(LeetCode の実体に合わせやすい)。 +> - **言語**:`LANGUAGE sql` にしてシンプルに。 + +--- + +## まだ落ちる場合のチェックリスト + +1. **方言が PostgreSQL になっているか** + ページ上部の切替が MySQL のままだと失敗します。 + +2. **同名関数の衝突** + 古い定義が残っていると意図しない解決になることがあります。必要なら一度落としてから再作成: + + ```sql + DROP FUNCTION IF EXISTS "NthHighestSalary"(INT); + DROP FUNCTION IF EXISTS nthhighestsalary(INT); + ``` + + → その後、上の `CREATE OR REPLACE` を実行。 + +3. **テーブル名の大文字クオート問題** + もし採点環境の実体が `"Employee"`(ダブルクオート付き)で作られていると、未クオート `employee` は見つかりません。 + その場合だけ、関数本体の参照を `"Employee"` に変えて再実行してください(通常は不要)。 + +4. **引数の型** + `2` は整数リテラルなので問題ありませんが、文字列で渡すと不一致になります。テスターは `2` を渡しているので通常 OK。 + +--- + +## 参考:DENSE_RANK 版(どちらの関数名でも通るように同様のラッパー付き) + +```sql +CREATE OR REPLACE FUNCTION nthhighestsalary(n INT) +RETURNS INT +LANGUAGE sql +STABLE +AS $$ + SELECT salary + FROM ( + SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rk + FROM (SELECT DISTINCT salary FROM employee) d + ) r + WHERE rk = n + LIMIT 1; +$$; + +CREATE OR REPLACE FUNCTION "NthHighestSalary"(n INT) +RETURNS INT +LANGUAGE sql +STABLE +AS $$ + SELECT nthhighestsalary(n); +$$; +``` + +--- + +これで `function nthhighestsalary(integer) does not exist` は解消できるはずです。もし依然としてランタイムエラーになる場合は、**今貼ったスニペットをそのまま**提出エディタに入れて実行した上で、出力ログ全文を見せてください。どこで解決に失敗しているかをピンポイントで特定します。 + +原因は「`n <= 0` の入力を `GREATEST(n-1,0)` で**切り上げ**てしまい、1 位を返していた」ことです。 +LeetCode の期待値は **`n <= 0` のときは `NULL` を返す** なので、先にガードして `NULL` を返すように直せば OK です。 + +## ✅ 修正版(PostgreSQL / LeetCode そのまま貼り付け可) + +```sql +-- 小文字版(本体) +CREATE OR REPLACE FUNCTION nthhighestsalary(n INT) +RETURNS INT +LANGUAGE plpgsql +STABLE +AS $$ +DECLARE + res INT; +BEGIN + -- ここが重要:n<=0 は不正入力として NULL を返す + IF n <= 0 THEN + RETURN NULL; + END IF; + + SELECT salary INTO res + FROM ( + SELECT DISTINCT salary + FROM employee + ORDER BY salary DESC + OFFSET n - 1 + LIMIT 1 + ) t; + + RETURN res; -- 見つからなければ res は NULL のまま → NULL を返す +END; +$$; + +-- CamelCase 互換名(LeetCode がこちらで呼んでもOK) +CREATE OR REPLACE FUNCTION "NthHighestSalary"(n INT) +RETURNS INT +LANGUAGE plpgsql +STABLE +AS $$ +BEGIN + RETURN nthhighestsalary(n); +END; +$$; +``` + +## 変更点の要旨 + +- `GREATEST(n-1,0)` をやめ、**`n <= 0` を明示的に `NULL` 返却**に変更 +- これにより `n = -1` などでも **`NULL`** を返し、期待に一致します +- 見つからない順位(例: n が distinct 件数を超える)も、`SELECT ... INTO res` で `res` が `NULL` のまま → **`NULL`** が返ります + +## 念のための手順(必要なら) + +```sql +-- 衝突回避(古い定義がある場合のみ) +DROP FUNCTION IF EXISTS "NthHighestSalary"(INT); +DROP FUNCTION IF EXISTS nthhighestsalary(INT); + +-- ↑を実行してから、修正版を貼ってください +``` + +これで `n = -1` のケースを含め、採点の “Wrong Answer 14/18” は解消できるはずです。