双方向ポインタで和 = K

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

双方向ポインタで和 = K

このレッスンで分かること

  • ソート済みなら全探索の罠を避けられる
  • ソート済みの配列 arr と整数 k が与えられたとき、arr[i] + arr[j] == k となるペアが 存在するか を判定したい
  • 左端に left = 0、右端に right = n - 1 を置きます

双方向ポインタで和 = K とは

ソート済み配列の両端から leftright を動かして、和が K になるペアを O(n) で見つける双方向ポインタ法を学ぶ。

ソート済みの配列 arr と整数 k が与えられたとき、arr[i] + arr[j] == k となるペアが 存在するか を判定したい。素直に O(n^2) の二重ループで全ペアを試すこともできますが、配列がソート済みであるという性質を利用すれば O(n) で解けます。それが 双方向ポインタ (two pointers) と呼ばれる定石テクニックです。

ソート済みなら全探索の罠を避けられる。両端からの 2 つのインデックスを動かすだけで線形時間に落ちる。

仕組みのイメージ

左端に left = 0、右端に right = n - 1 を置きます。和 sum = arr[left] + arr[right]k と比較し、次の 3 通りに分岐します。

  • sum == k のとき、ペアが見つかったので true を返す
  • sum < k のとき、和が足りないので left を 1 つ右にずらして大きくする
  • sum > k のとき、和が大きすぎるので right を 1 つ左にずらして小さくする

leftright がぶつかるまでこれを繰り返し、見つからなければ false を返します。1 回のループで必ず leftright が動くため、計算量は O(n) です。

動きを図で追う

diagram (will load when visible)

なぜソートが必要か

ソート済みでない配列で同じ手順をやっても、left を右にずらしたら和が増えるとは限らないため判断ができません。単調性 があるからこそ「動かす方向」を一意に決められます。ソートされていないなら、まず O(n log n) でソートしてから両端探索する、または set を使う方法 (こちらは O(n) だがメモリを使う) に切り替えます。

ソート済み + 単調性 = 双方向ポインタが効く。これは lower_boundslide window の親戚にあたる重要パターン。

Python の実装

Python

def hasPairWithSum(arr, k): left = 0 right = len(arr) - 1 while left < right: total = arr[left] + arr[right] if total == k: return True if total < k: left += 1 else: right -= 1 return False

arr が空、または 1 要素の場合でも left < right の条件で安全に False を返します。

JavaScript の実装

JavaScript

function hasPairWithSum(arr, k) { let left = 0; let right = arr.length - 1; while (left < right) { const total = arr[left] + arr[right]; if (total === k) return true; if (total < k) left += 1; else right -= 1; } return false; }

よくある間違い

1 つ目は ソートされていない配列 で双方向ポインタを使ってしまうことです。例えば [3, 1, 0]k = 4 を探すと、ペア 3+1=4 が存在するのにアルゴリズムは false を返してしまいます。

2 つ目は left <= right と書いてしまい、同じ要素を 2 度足してしまうケースです。問題によっては自分自身を使って良い場合もありますが、本問では 異なる 2 要素 という前提なので left < right が正解です。

3 つ目は 重複要素 の扱いです。ペアの組み合わせを すべて列挙 したいときには、重複をスキップする処理を追加する必要があります。

応用パターン

双方向ポインタは「ペアの和」だけでなく、次のような問題でも使えます。

  • 回文判定 (両端から文字を比較して left == right まで進める)
  • 3 つの和 = K (1 つ固定して残り 2 つを双方向ポインタで)
  • ソート済み配列のマージ (片方の先頭ともう一方の先頭を比較)
  • 雨水を溜める問題 (左右の最大値を更新しながら進める)

どれも「両端を動かして単調性を活かす」という発想は共通しています。O(n^2) の素朴な解法を見たら、まず「ソートや単調性を使って O(n)O(n log n) に落とせないか」を考える癖をつけてください。

やってみよう

  • [1, 3, 4, 5, 7]k = 8 で動きを手で追ってみる。left=0(1)、right=4(7) の時点で 1 + 7 = 8 のペアが最初に見つかるはずです。
  • [] のような空配列で false が返るか確認する。
  • 計算量が O(n) であることを、while ループ内で leftright が必ず 1 動くこと、合わせて最大 n 回しか動かないことから説明できるかを考えてみる。
  • ソートされていない配列にこの関数を適用したら、何が問題になるかを考えてみる。

双方向ポインタは「ソート済み + 線形時間」の鉄板パターン。配列・文字列問題で頻出するので体に染み込ませよう。

よくある質問

Q. 双方向ポインタはいつ使う?

A. ソート済み配列で和や差が特定値になるペアを探すときに O(n) で解けます。回文判定、コンテナで最大の水を保持する問題(Container With Most Water)など、両端から評価する系の問題で活躍します。事前にソートが必要な点に注意してください。

Q. スライド窓との違いは?

A. 両者ともポインタを 2 つ使いますが、双方向ポインタは両端から内側に動かす(主にソート済み)、スライド窓は両方左から右へ動かす(連続区間集約)という違いがあります。本質的に「複数ポインタを使う」発想は同じです。

Q. ハッシュマップとどう使い分けますか?

A. ソート済みなら双方向ポインタ O(n) + 空間 O(1)、未ソートならハッシュマップ O(n) + 空間 O(n) が候補です。前処理にソートを許容できるなら双方向ポインタが空間効率で勝ち、許容できないならハッシュマップが時間効率で勝ちます。

次のレッスン

次は スライド窓の最大和 で、スライド窓の最大和 を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. 双方向ポインタ の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. 双方向ポインタ とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. arr は昇順ソート済みの整数配列であると仮定して良い
  2. 計算量は O(n) であること (双方向ポインタ法を使う)
  3. left と right が同じインデックスを指すケースは無効 (i != j)

入出力例

test-cases.txt

hasPairWithSum([1,2,3,4,5], 7)true hasPairWithSum([1,2,3,4,5], 10)false hasPairWithSum([1,3,4,5,7], 8)true hasPairWithSum([1,3,4,5,7], 2)false hasPairWithSum([2,4], 6)true hasPairWithSum([2], 2)false hasPairWithSum([-3,-1,2,4], 1)true

ヒント

main.py
main.py
学習モード

メモ

双方向ポインタで和 = K

⌘S で保存