スライディングウィンドウ:「窓が何を保つか」を先に決める

窓が成立するのは、窓の妥当性が左右の端に対して単調に変わる場合です。保つ状態と縮める条件を先に書き出せば、2 ポインタの二重構造は自然に O(n) に償却されます。

スライディングウィンドウの難所は 2 ポインタではなく、窓が何を保つかにあります。そこが決まれば二重構造は自然にまとまり、決まらなければ同じ文字を何度も数え直す実装になります。

雛形:右を伸ばし、左を縮める

「重複のない最長部分文字列」を例にします。窓は [left, right] で、保つ状態は各文字が最後に現れた添字です。右端が 1 つ進むたび、その文字が窓の中に既にあれば、左端をその次の位置まで飛ばします。

function longestUnique(s) {
  const seen = new Map();
  let left = 0;
  let best = 0;
  for (let right = 0; right < s.length; right += 1) {
    const ch = s[right];
    const last = seen.get(ch);
    if (last !== undefined && last >= left) left = last + 1;
    seen.set(ch, right);
    best = Math.max(best, right - left + 1);
  }
  return best;
}

last >= left の判定が要点です。文字がもっと前に窓の外で現れている場合、今の窓とは無関係なので左端は動かしません。初心者がよく書くのは seen.has(ch) だけで止める実装です。

なぜ O(n) か

right も left も右にしか進まず、戻りません。right は n 歩、left も合計で高々 n 歩です。したがって二重に見えても総操作数は 2n 以下です。ただし左端を進めても縮小条件が逆転しないことが前提です。

使うべきでない場面

窓の妥当性が大きさに対して単調でない場合、つまり大きくすると妥当にも不正にもなり規則性がない場合、左端を進めても妥当性は戻らず 2 ポインタは成立しません。状態の設計を変えて不正を単調な量として表すか、累積和・単調キュー・二分探索に切り替えます。

同じ雛形の 3 変種

問題 保つ状態 縮める条件
重複のない最長部分文字列 各文字の最終添字 重複が現れた
最小被覆部分文字列 文字ごとの不足数 すべて覆った
固定長部分配列の最大和 窓の和 長さが k を超えた

最小被覆の問題では、答えの更新は伸ばすときではなく縮めるループの中で行います。最短の妥当な窓は縮めている最中のどこでも現れうるからです。

妥当性は単調であること。右端を伸ばすと妥当から不正へ(またはその逆へ)移り、左端を縮めれば元に戻せること。

← 記事一覧に戻る

コメント

…