Skip to content

Latest commit

 

History

History
155 lines (103 loc) · 5.33 KB

File metadata and controls

155 lines (103 loc) · 5.33 KB

双方向ポインタ(Two Pointers)について詳しく解説

1. 双方向ポインタとは?

双方向ポインタ(Two Pointers) とは、配列の異なる位置にある2つのポインタ(変数 ij など)を使い、データを効率的に探索するアルゴリズムの手法です。

この手法は ソート済みのデータに対して特に有効 で、以下のような問題で使われます:

  • 特定の条件を満たすペアを数える
  • 2つの数の和が特定の値になるかを判定する
  • 区間の合計や条件を満たす部分列を探索する

2. 基本的な考え方

2つのポインタをどう動かすか

  1. ポインタ i(左側)を固定し、ポインタ j(右側)を動かす
    • 例えば、A[i] を固定して、A[j] を条件を満たす最大の位置まで動かす。
  2. 条件を満たさなくなったら i を動かす
    • A[j] - A[i] > K になったら i を進める。

3. 本題の「差が K 以下の整数ペアの数え方」

この問題では、A[j] - A[i] <= K を満たすペア (A[i], A[j]) の数を求めます。

アルゴリズムの流れ

  1. ij の2つのポインタを用意(i=0 からスタート、j0 からスタート)。
  2. j を右に動かし、A[j] - A[i] <= K を満たす最大の j を見つける。
  3. そのとき A[i] と組み合わせられるのは A[i+1] から A[j-1] までの (j - i - 1) 個。
  4. i を1つ進めて再計算する。
  5. iN-1 になるまで繰り返す。

4. コードの詳細解説

function countPairsTwoPointers(N, K, A) {
    let count = 0;
    let j = 0; // jはiとともに進めるポインタ

    for (let i = 0; i < N; i++) {
        while (j < N && A[j] - A[i] <= K) {
            j++; // 条件を満たす限り j を進める
        }
        count += j - i - 1; // (j - i - 1) 個のペアが作れる
    }

    console.log(count);
}

// 入力例
const [N, K, ...A] = `5 3
1 2 4 5 8`
    .split(/\s+/)
    .map(Number);
countPairsTwoPointers(N, K, A);

5. 具体的な動作を詳しく説明

入力例

N = 5, K = 3
A = [1, 2, 4, 5, 8]

処理の流れ

i j の初期値 j の最大位置 (j - i - 1) count の累計
0 (A[0] = 1) 0 3 (A[3] = 5) 3 - 0 - 1 = 2 2
1 (A[1] = 2) 3 3 (A[3] = 5) 3 - 1 - 1 = 1 3
2 (A[2] = 4) 3 4 (A[4] = 8) 4 - 2 - 1 = 1 4
3 (A[3] = 5) 4 4 (A[4] = 8) 4 - 3 - 1 = 0 4

最終的に、ペア数は 4 となります。


6. 計算量の分析

  • iN 回ループする。
  • jN 回以内に右端まで進む。
  • 合計 O(N) で解ける。

これは 二分探索(O(N log N))よりも高速 で、大きな N(最大 100000)でも高速に動作します。


7. まとめ

双方向ポインタ(Two Pointers)とは?

  • 2つのポインタを使って効率的に探索 するアルゴリズム。
  • ソート済みデータ に適用すると O(N) で高速に解ける

この問題での適用方法

  • i を固定し、j を動かして A[j] - A[i] <= K を満たす最大の j を見つける。
  • A[i] と組み合わせられるのは (j - i - 1) 個。
  • O(N) の時間計算量 で解ける。

count += (j - i - 1); の処理について解説

この部分は、A[i] を固定したときに、差が K 以下となる A[j] の個数を数える処理 です。

具体的な動作

  1. i を左ポインタとして 0 から順に動かす
  2. j を右ポインタとして A[j] - A[i] <= K を満たす間、右に進める
  3. j が条件を満たさなくなったら、その j までに作れるペア数 (j - i - 1) を加算する

なぜ (j - i - 1) なのか?

  • j - i ではなく j - i - 1 になっている理由は、A[j] は条件を満たさない j の位置にあるため、カウントしないようにするためです。
  • A[i] と組み合わせる相手は A[i+1] から A[j-1] まで なので、j-1 - i を求めるために -1 している。

具体例で理解

入力

N = 5, K = 3
A = [1, 2, 4, 5, 8]

処理の流れ

i (固定) j の最大位置 (j - i - 1) カウント加算
i = 0 (A[0] = 1) j = 3 (A[3] = 5) 3 - 0 - 1 = 2 count += 2
i = 1 (A[1] = 2) j = 3 (A[3] = 5) 3 - 1 - 1 = 1 count += 1
i = 2 (A[2] = 4) j = 4 (A[4] = 8) 4 - 2 - 1 = 1 count += 1
i = 3 (A[3] = 5) j = 4 (A[4] = 8) 4 - 3 - 1 = 0 count += 0

最終的に count = 4 となります。


まとめ

  1. jA[j] - A[i] <= K を満たす最大の位置まで進める
  2. i 固定時に、A[i+1] から A[j-1] までの (j - i - 1) 個 のペアが作れる
  3. 計算量は O(N)