lower_bound (最初に >= target の位置)

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

lower_bound (最初に >= target の位置)

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

  • 「等しい値があったら即 return」ではなく、「境界まで詰めきってから return」が lower_bound の本質です
  • lower_boundソート 済み配列で「target 以上の値が最初に現れる index」を返す関数です
  • 配列の長さを n として、戻り値は次のいずれかになります

lower_bound とは

ソート済み配列で target 以上の値が最初に現れる index を返す lower_bound を二分探索で実装します。

lower_boundソート 済み配列で「target 以上の値が最初に現れる index」を返す関数です。二分探索 の応用で、計算量O(log n)C++std::lower_boundPythonbisect.bisect_left と同じ概念です。Java には直接の相当物はなく、Collections.binarySearch は要素が見つからない場合に -(挿入位置)-1 を返す仕様であり、重複時に左端を保証しないため変換が必要です。

普通の二分探索は「等しい値を探す」、lower_bound は「境界を探す」。応用範囲が広く、競プロでも実務でも頻出のテクニックです。

戻り値の仕様

配列の長さを n として、戻り値は次のいずれかになります。

  • 配列内に target 以上の要素がある場合、その最初の位置
  • 配列内に target 以上の要素がない場合、n (末尾を 1 つ超えた位置)

lower_bound の便利なところは、target が配列に存在しなくても 挿入位置 が分かることです。ソート を維持したまま target を挿入するなら、戻り値の位置に入れれば OK です。

配列 [1, 3, 5, 5, 7, 9] に対して:

targetlower_bound の戻り値意味
00全要素が target より大きい
525 が最初に出現する位置
645 より大きい最小の値 (7) の位置
959 の最初の位置
106該当なし、配列末尾を超えた位置

実装

lower_bound二分探索 のテンプレを少しいじるだけで作れます。条件は arr[mid] < targetarr[mid] >= target か、です。

Python

def lowerBound(arr, target): low, high = 0, len(arr) while low < high: mid = (low + high) // 2 if arr[mid] < target: low = mid + 1 else: high = mid return low

JavaScript

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

区間の閉じ方の違い

通常の二分探索 (等しい値を探す版) と lower_bound では 高い側 (high) の初期値と更新方法が異なります。

diagram (will load when visible)
  • high の初期値は n (n-1 ではない)
  • ループ条件は low < high (<= ではない)
  • mid を見つけても return しない (境界を求めるため最後まで詰める)

「等しい値があったら即 return」ではなく、「境界まで詰めきってから return」が lower_bound の本質です。

境界条件のチェック

  • target がすべての要素より小さい → 0 を返す
  • target がすべての要素より大きい → n を返す
  • target が配列内に複数ある → 最初に現れる位置 を返す

upper_bound (target より大きい最初の位置) も似た形で書けます。upper_bound - lower_boundtarget出現回数 が求まる、というのは競プロの常套句です。

比較

機能戻り値
index (普通の二分探索)一致する位置、なければ -1
lower_bound>= target の最初の位置、なければ n
upper_bound> target の最初の位置、なければ n

よくある間違い

  • high = mid - 1 にしてしまい、境界が 1 ずれる
  • low <= high にしてしまい、無限ループ に陥る
  • target が見つかったら即 return してしまい、最初の位置ではなく途中の位置を返してしまう
  • Javaint mid = (low + high) / 2; と書いて オーバーフロー が起きる (low + (high - low) / 2 を使う)

やってみよう

[1, 3, 5, 5, 7] に対して 5 で呼ぶと 2 が返ります。6 なら 4100 なら 50 なら 0 です。重複 がある場合は 最初の位置 を返すことを忘れずに。

lower_bound を覚えると、ソート済みデータ への問い合わせのほとんどが O(log n) で書けるようになります。

よくある質問

Q. PUT と PATCH はどう使い分けますか?

A. PUT はリソース全体を置き換え、PATCH は部分更新です。例えばユーザー情報全体を送るなら PUT、メールアドレスだけ変えるなら PATCH が REST の慣習に沿います。冪等性は PUT が保証、PATCH は実装次第になります。

Q. 冪等性とは何ですか?

A. 同じリクエストを何度送っても結果が変わらない性質です。GET / PUT / DELETE は冪等、POST は通常冪等ではありません。ネットワークエラーで再送される可能性がある API では、冪等な操作にしておくと安全です。idempotency-key ヘッダで明示する手法もあります。

Q. DELETE のリクエストにボディを入れて良い?

A. HTTP 仕様上は許されていますが、扱わないライブラリやプロキシも多いため避けるのが無難です。複雑な削除条件はクエリパラメータか、POST /resource/delete のようなアクションエンドポイントを使うとトラブルを避けられます。

次のレッスン

次は ピーク要素検索 で、隣接要素より大きい要素(ピーク)を二分探索で O(log n) で見つける手法を学びます。

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

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

理解度チェック (30 秒)

Q. lower_bound とは何か、1 文で説明してください。

この章のポイント

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

関連レッスン

要件

  1. 二分探索 (O(log n)) で実装する
  2. 見つからない場合は配列長 n を返す
  3. 重複がある場合は最初の位置を返す

入出力例

test-cases.txt

lowerBound([1,3,5,5,7,9], 5)2 lowerBound([1,3,5,5,7,9], 6)4 lowerBound([2,4,6,8], 1)0 lowerBound([2,4,6,8], 10)4 lowerBound([1,2,3,4,5], 5)4 lowerBound([3], 3)0

ヒント

main.py
main.py
学習モード

メモ

lower_bound (最初に >= target の位置)

⌘S で保存