アルゴリズム概要

💡 この問題を一言で言うと

「整数リスト nums の中から、合計が target になる 2 つの要素を見つけ、そのインデックス(位置番号)を返す問題」です。答えは必ず 1 組だけ存在することが保証されています。

⚠️ なぜ単純な「全探索」では不十分なのか

  • 全ての組み合わせを試す二重ループは O(n²) になる。n=10,000 のとき最悪 1 億回の比較が発生し、制限時間に引っかかる可能性がある
  • Follow-up では「O(n²) より速い解法」が明示的に求められている
  • 解決策:辞書(ハッシュテーブル)を使えば「補数がすでに出現したか」を O(1) で確認でき、全体を O(n) に削減できる
O(n)
時間計算量
O(n)
空間計算量
dict
データ構造
1-pass
走査回数

入出力例

Example 1

nums = [2,7,11,15]
target = 9

→ [0, 1]

nums[0]+nums[1] = 2+7 = 9 ✅

Example 2

nums = [3,2,4]
target = 6

→ [1, 2]

nums[1]+nums[2] = 2+4 = 6 ✅

Example 3(重複値)

nums = [3,3]
target = 6

→ [0, 1]

同じ値でも異なるインデックス ✅

📌 制約

  • 2 <= nums.length <= 10⁴
  • -10⁹ <= nums[i] <= 10⁹
  • -10⁹ <= target <= 10⁹
  • 答えは必ず 1 組だけ存在する

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

各ステップをクリックするか ▶ Play で自動再生できます。

Python 実装

📋 このコードの構造(先に全体像を把握しよう)

  1. seen = {} という空の辞書(メモ帳)を用意する
  2. enumerate() でインデックスと値を同時に取り出しながらループする
  3. 補数(target - num)が seen にあれば答えを返す
  4. なければ今の数を seen に記録して次へ
from __future__ import annotations
from typing import List


class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # {数値: そのインデックス} を記録する「メモ帳」を用意する。
        # dict は CPython 内部でハッシュテーブルを使っており
        # キー検索が O(1)(一定時間)で完了する。
        # リストの `in` 演算(O(n))より大幅に速い。
        seen: dict[int, int] = {}

        # enumerate() でインデックス i と値 num を同時に取得する。
        # C 実装なので手書きの for+range より高速で可読性も高い。
        for i, num in enumerate(nums):

            # 「target から今の数を引いた値」= 補数(complement)。
            # もし補数が seen にあれば、そのペアが答えになる。
            complement: int = target - num

            if complement in seen:
                # seen[complement] → 補数のインデックス(過去に記録)
                # i               → 現在のインデックス
                return [seen[complement], i]

            # ペアが見つからなければ今の数を記録して次へ。
            # 後のループで「この数が誰かの補数」として参照される。
            seen[num] = i

        # 問題の制約上ここには到達しないが pylance 警告抑制のため。
        raise ValueError("No valid pair found.")

▶ 入力例 nums=[2,7,11,15], target=9 での動作トレース

初期状態: seen = {}

i=0, num=2
  complement = 9 - 2 = 7
  7 in {} → No
  seen = {2: 0}

i=1, num=7
  complement = 9 - 7 = 2
  2 in {2: 0} → Yes ✅
  return [seen[2], 1] = [0, 1]

出力: [0, 1]
  → nums[0]=2 と nums[1]=7 の和が 9 になる

処理フローチャート

🗺️ フローチャートの読み方

楕円(緑)= 開始・終了
四角(青)= 処理ステップ
ひし形(黄)= 条件分岐
緑=はい 赤=いいえ
%%{init: { "theme": "base", "themeVariables": { "primaryColor": "#e0f2fe", "primaryTextColor": "#0c4a6e", "primaryBorderColor": "#0284c7", "lineColor": "#64748b", "secondaryColor": "#fef3c7", "tertiaryColor": "#ede9fe", "edgeLabelBackground": "#f8fafc", "fontFamily": "Noto Sans JP, sans-serif", "fontSize": "15px" } }}%% flowchart TD S(["① 開始 twoSum"]) Init["② seen = {} を初期化"] Loop{"③ 次の要素あり? i, x を取得"} Calc["④ need = target − x を計算"] Check{"⑤ need が seen にあるか?"} Return["⑥ seen[need], i を返却 ✅"] Record["⑦ seen[x] = i を登録"] Done["配列終了 ※制約上は到達しない ValueError を送出"] End(["⑧ 終了"]) S --> Init Init --> Loop Loop -->|"はい(要素あり)"| Calc Loop -->|"いいえ(配列終了)"| Done Calc --> Check Check -->|"はい — 辞書検索 O(1)"| Return Check -->|"いいえ"| Record Record -->|"次の要素へ(ループバック)"| Loop Return --> End Done --> End style S fill:#d1fae5,stroke:#10b981,color:#065f46,font-weight:bold style End fill:#d1fae5,stroke:#10b981,color:#065f46,font-weight:bold style Init fill:#e0f2fe,stroke:#0284c7,color:#0c4a6e style Calc fill:#e0f2fe,stroke:#0284c7,color:#0c4a6e style Loop fill:#fef3c7,stroke:#f59e0b,color:#713f12 style Check fill:#fef3c7,stroke:#f59e0b,color:#713f12 style Return fill:#d1fae5,stroke:#059669,color:#064e3b,font-weight:bold style Record fill:#ede9fe,stroke:#7c3aed,color:#4c1d95 style Done fill:#fee2e2,stroke:#dc2626,color:#991b1b

🔎 入力例 nums=[2,7,11,15], target=9 でのフロー追跡

  1. 「開始 twoSum」ノード → nums=[2,7,11,15], target=9 を受け取る
  2. 「seen={} 初期化」→ 空の辞書を用意する
  3. 「配列を走査」→ i=0, x=2 を取得(要素あり)
  4. 「need = 9-2 = 7 を計算」
  5. 「need(7) が seen にあるか?」→ seen={} なので いいえ
  6. 「x(2) が seen にあるか?」→ いいえ → seen[2]=0 を登録 → ループバック
  7. 「配列を走査」→ i=1, x=7 を取得(要素あり)
  8. 「need = 9-7 = 2 を計算」
  9. 「need(2) が seen にあるか?」→ seen={2:0} に 2 がある → はい
  10. 「[seen[2], 1] = [0, 1] を返却」→「終了」ノードへ
フローの説明:
1. 空の辞書 seen を初期化
2. 配列を左から順に走査(各要素を x、添字を i とする)
3. 補数 need = target - x を計算
4. need が辞書に存在するか確認 → 存在すれば即座に [seen[need], i] を返却
5. 存在しなければ、seen[x] = i を実行して現在の値を記録(同値があればインデックスを更新)
6. 次の要素へ進む(ループバック)
7. 全要素を処理しても解が見つからなければ ValueError を送出(問題前提では到達しない)

計算量分析

📖 Big-O 記法の読み方(入力 n が増えると処理時間がどう変わるかの目安)

O(1)
常に一定
例:辞書の直接引き
O(n)
入力に比例
例:リストを1回走査
O(n log n)
nより少し多い
例:ソート処理
O(n²)
入力の2乗
例:二重ループ
アプローチ 時間計算量 空間計算量 可読性 備考
二重ループ(全探索) O(n²) O(1) ★★★ Follow-up の制約を満たさない
✅ ハッシュマップ 1 パス O(n) O(n) ★★★ 推奨 — 最速かつシンプル

🔍 なぜ O(n) になるのか

nums1 度だけ先頭から末尾まで走査します(= O(n))。 各イテレーションで行う「辞書の検索」と「辞書への記録」はどちらも O(1) です。 したがって全体の時間計算量は O(n) × O(1) = O(n) になります。 空間計算量は最悪ケース(答えが末尾 2 要素のとき)に n-1 個の数値を辞書に記録するため O(n) です。

📖 用語集

このページで登場した専門用語を五十音順でまとめました。分からない言葉が出てきたときに参照してください。

インデックス
リストや配列の何番目にあるかを示す番号。Pythonでは先頭の要素が 0 から始まります。例:nums[0] は先頭の要素。
O(1)・O(n)・O(n²) — Big-O記法
処理にかかる時間・メモリが入力の大きさ n に対してどう増えるかを表す記法。
O(1):入力サイズに関わらず一定(最速)
O(n):入力が2倍になると処理も約2倍
O(n²):入力が2倍になると処理は約4倍(二重ループに多い)
enumerate()
リストを回しながら「何番目か(インデックス)」と「値」を同時に取り出す Python の組み込み関数。C 実装のため高速です。for i, val in enumerate(nums): のように使います。
型ヒント(Type Hints)
関数の引数・戻り値に型を注釈する仕組み。def f(x: int) -> str: のように書きます。pylance(VSCode の型チェッカー)が実行前に型の不一致を検出できるようになります。
補数(complement)
今の数 num とペアになるべき数値。complement = target - num で計算します。例:target=9, num=2 のとき補数=7。
ハッシュテーブル(Hash Table)
キーを特殊な数値(ハッシュ値)に変換し、値の格納場所を一瞬(O(1))で特定できるデータ構造。図書館の索引カードに例えると、タイトル(キー)から棚番号(値)を即座に引けるイメージです。Pythonでは dict がこれにあたります。
CPython
最も広く使われるPythonの実装。C言語で書かれており、dictenumerate() などの組み込み関数の多くがC実装のため高速です。本解説が対象とする環境です。
走査(scan)
リストや配列を先頭から末尾まで順番に見ていくこと。「1パス走査」とは、リストを1度だけ端から端まで見ることを意味します。