回転ソート配列での探索

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

回転ソート配列での探索

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

  • 毎回区間が半分になるので O(log n)、空間は O(1) です
  • 昇順 ソート された配列を、ある位置でぐるっと回した配列を 回転ソート配列 と呼びます
  • 回転ソート配列 を真ん中で割ると、必ず 片側は完全に ソート 済み になります

回転ソート配列での探索 とは

昇順ソートされた配列がある位置で回転している配列に対し、target の index を O(log n) で探します。

昇順 ソート された配列を、ある位置でぐるっと回した配列を 回転ソート配列 と呼びます。例えば [1, 2, 3, 4, 5] を 2 回転 (左に 2 つシフト) したら [3, 4, 5, 1, 2] です。重複なし で考えるのが定番の問題設定です。

LeetCode 33. Search in Rotated Sorted Array として有名な問題です。O(log n) で解けることが要求されます。

観察

回転ソート配列 を真ん中で割ると、必ず 片側は完全に ソート 済み になります。

例えば [4, 5, 6, 7, 0, 1, 2]mid=3 (7) で割ると、左側 [4,5,6,7]ソート 済み、右側 [0,1,2]ソート 済み。一般化すると、mid を境界にして:

  • arr[low] <= arr[mid] なら 左半分ソート 済み
  • そうでなければ 右半分ソート 済み

そして ソート 済みの方に target が範囲内に含まれるかを >= <= でチェックすれば、進むべき方向が決まります。

コード

Python

def rotatedSearch(arr, target): low, high = 0, len(arr) - 1 while low <= high: mid = (low + high) // 2 if arr[mid] == target: return mid if arr[low] <= arr[mid]: # 左がソート済み if arr[low] <= target < arr[mid]: high = mid - 1 else: low = mid + 1 else: # 右がソート済み if arr[mid] < target <= arr[high]: low = mid + 1 else: high = mid - 1 return -1

JavaScript

function rotatedSearch(arr, target) { let low = 0; let high = arr.length - 1; while (low <= high) { const mid = Math.floor((low + high) / 2); if (arr[mid] === target) return mid; if (arr[low] <= arr[mid]) { if (arr[low] <= target && target < arr[mid]) high = mid - 1; else low = mid + 1; } else { if (arr[mid] < target && target <= arr[high]) low = mid + 1; else high = mid - 1; } } return -1; }

図解

diagram (will load when visible)

計算量

毎回区間が半分になるので O(log n)、空間は O(1) です。回転 がない普通の ソート 済み配列でも同じコードが動きます (左半分 は常に ソート 済みとして扱われます)。

比較

配列線形探索普通の二分探索回転対応版
ソート 済みO(n)O(log n)O(log n)
回転ソートO(n)動かないO(log n)
未ソートO(n)動かない動かない

回転 があるかないかを事前に判定する必要はありません。今回のアルゴリズムはどちらの場合も正しく動きます。

注意するポイント

  • arr[low] <= arr[mid]等号 を忘れない (要素 2 個や mid == low のケースで重要)
  • target の範囲チェックは 左閉右開 (arr[low] <= target < arr[mid]) のような片側の境界に注意
  • 重複あり回転ソート配列 だと O(log n) を保証できなくなる (本問題では 重複なし 前提)

よくある間違い

  • arr[mid] == target の判定を入れ忘れる
  • 等号 (<=) と狭義不等号 (<) を間違える
  • target == arr[low]target == arr[high] の境界判定を入れ忘れる
  • 配列の長さ 01 のケースで index out of range

やってみよう

[4, 5, 6, 7, 0, 1, 2] から 0 を探すと index=43 を探すと -1[5, 1, 3] から 3 を探すと index=2[1] から 0 を探すと -1

本問題のテストでは 重複なし 配列のみを使うので、上記のシンプルな実装で十分です。

業務的には 回転ソート配列 という構造は珍しいですが、循環バッファ カレンダーのループ表示 音楽の再生位置 など、本質的に 回転 が関わるデータ構造はそこかしこにあります。考え方を覚えておくと応用がきくテクニックです。

よくある質問

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

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

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

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

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

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

次のレッスン

次は 第2章まとめクイズ で、昇順ソートされた配列がある位置で回転している配列に対し、target の index を O(log n) で探します を学びます。

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

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

理解度チェック (30 秒)

Q. 回転ソート探索 とは何か、1 文で説明してください。

この章のポイント

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

関連レッスン

要件

  1. O(log n) で実装する
  2. 重複なし配列を前提とする
  3. 見つからない場合は -1 を返す

入出力例

test-cases.txt

rotatedSearch([4,5,6,7,0,1,2], 0)4 rotatedSearch([4,5,6,7,0,1,2], 5)1 rotatedSearch([4,5,6,7,0,1,2], 3)-1 rotatedSearch([1,2,3,4,5], 3)2 rotatedSearch([1], 1)0 rotatedSearch([1], 0)-1

ヒント

main.py
main.py
学習モード

メモ

回転ソート配列での探索

⌘S で保存