ソート済み 2 配列のマージ

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

ソート済み 2 配列のマージ

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

  • マージ (merge) は、2 つのソート済み配列 を 1 本のソート済み配列に結合する操作です
  • 素朴な発想は次の通りです
  • これは動きますが、O((n + m) log(n + m)) かかります

ソート済み 2 配列のマージ とは

2 つの ソート済み配列 を 1 本のソート済み配列にマージする 双方向ポインタ の典型題を実装する。

マージ (merge) は、2 つのソート済み配列 を 1 本のソート済み配列に結合する操作です。マージソート の心臓部であり、外部ソートデータベースmerge joinタイムライン統合 などで使う基礎テクニックです。本レッスンでは O(n + m) でこれを実装し、双方向ポインタ の感覚を体に染み込ませます。

「ソート済みである」という前提が、なぜ計算量を O(n log n) から O(n + m) に落とせるのかを意識して読み進めよう。

なぜ単純結合 + ソートではダメか

素朴な発想は次の通りです。

Python

def mergeSorted(a, b): return sorted(a + b)

これは動きますが、O((n + m) log(n + m)) かかります。ab が既に ソート済み という情報を捨てているからです。一方、双方向ポインタを使えば O(n + m) で済みます。n = m = 10^6 のとき、ざっくり 20 倍近い差です。

双方向ポインタの考え方

a を指すポインタ ib を指すポインタ j を用意し、a[i]b[j] の小さい方を 結果配列 に積みます。積んだ側のポインタを 1 進める。これを i == len(a) または j == len(b) になるまで繰り返し、最後に残った片方の配列を後ろにまるごと連結すれば完成です。

diagram (will load when visible)

図のポイント (テキスト併記)

  • 各要素はちょうど 1 回ずつ 比較pop を経るので、合計 O(n + m) で確定します

各要素はちょうど 1 回ずつ 比較pop を経るので、合計 O(n + m) で確定します。

Python での実装

Python

def mergeSorted(a, b): i, j = 0, 0 result = [] while i < len(a) and j < len(b): if a[i] <= b[j]: result.append(a[i]) i += 1 else: result.append(b[j]) j += 1 result.extend(a[i:]) result.extend(b[j:]) return result

while のループ後に、残ったほうの配列を extend で追加します。a が先に尽きたなら b[j:] がそのまま残り、その逆もしかり、です。

JavaScript での実装

JavaScript

function mergeSorted(a, b) { let i = 0, j = 0; const result = []; while (i < a.length && j < b.length) { if (a[i] <= b[j]) result.push(a[i++]); else result.push(b[j++]); } while (i < a.length) result.push(a[i++]); while (j < b.length) result.push(b[j++]); return result; }

while を 3 つに分割しても OK ですし、while (i < a.length || j < b.length) の中で a[i] ?? Infinity のようにしてもよいですが、上記のように 2 段書き のほうが読みやすいです。

安定性とタイブレーク

a[i] <= b[j]<=< にしてはいけない。タイブレークで a を優先しないと 安定ソート の性質が崩れる。

マージ分割統治 の典型例。マージソートO(n log n) は、この mergelog n 段繰り返すから得られる。

安定 (stable) というのは、同じキー の要素同士で元の 順序 が保たれることです。マージソート安定ソート と呼ばれるのは、まさにこの <= のおかげです。

よくある間違い

  • 残った側の連結を忘れる。while を抜けた後、ab のどちらかには必ず要素が残っている可能性がある。
  • < で比較してしまい、安定性 が壊れる (テストには影響しないが、本番では事故になる)。
  • 結果配列を a に上書きしてしまい、i のインデックスがずれる。必ず新しい配列を用意する。

やってみよう

mergeSorted(a, b) を実装してください。a, b はそれぞれ 昇順ソート済み の整数配列です。sorted() / Array.prototype.sort は使わず、双方向ポインタO(n + m) で書いてください。nullundefined は来ません。空配列が来てもよいように while の境界条件を意識しましょう。

よくある質問

Q. このトピックは実務でどう役立ちますか?

A. DB のクエリ最適化、API 設計、データ構造の選択など、設計判断の根拠になります。表面的にライブラリを使うだけでなく「なぜそれが速いのか」を理解できると、性能問題を未然に防げます。コーディング面接でも頻出のテーマです。

Q. 計算量はどう求めれば良いですか?

A. ループのネストごとに掛け算する、再帰なら漸化式から解く、というのが基本です。Big-O 表記は定数倍と低次の項を無視するため、n の指数(n²、n log n など)に注目してください。最悪・平均・最良の 3 つを意識すると説得力が増します。

Q. 覚えるべき定番アルゴリズムは何ですか?

A. 二分探索、クイックソート/マージソート、BFS/DFS、ダイクストラ、DP の基本問題(フィボナッチ・ナップサック)が必修です。これらを「白紙から書ける」状態にすると、応用問題が一気に解けるようになります。

次のレッスン

次は 配列の k 回転 で、配列を右へ k 回シフトする「回転 (rotate)」を、スライスと三段反転の 2 通りで実装する方法を学びます。

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

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

理解度チェック (30 秒)

Q. 2配列マージ とは何か、1 文で説明してください。

この章のポイント

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

関連レッスン

要件

  1. 関数 mergeSorted(a, b) を実装し、昇順にソートされた配列を返す
  2. 双方向ポインタ を使い、計算量は O(n + m) であること (sorted() / Array.sort を使わない)
  3. 同値の要素は a を先に置く 安定 なマージを行うこと

入出力例

test-cases.txt

mergeSorted([1,3,5], [2,4,6])[1,2,3,4,5,6] mergeSorted([1,2,3], [4,5])[1,2,3,4,5] mergeSorted([1,2,2,5], [2,3,4])[1,2,2,2,3,4,5] mergeSorted([1,2,3], [10,11,12])[1,2,3,10,11,12] mergeSorted([5], [3])[3,5] mergeSorted([1], [2,3,4,5,6])[1,2,3,4,5,6]

ヒント

main.py
main.py
学習モード

メモ

ソート済み 2 配列のマージ

⌘S で保存