もちろんです。Python の bisect モジュールは、ソート済みのリストに対して高速な二分探索操作を提供する便利な標準ライブラリです。
以下の4つが代表的な関数です:
| 関数 | 説明 |
|---|---|
bisect_left(a, x) |
x を挿入すべき最も左の位置(index)を返す |
bisect_right(a, x) または bisect(a, x) |
x を挿入すべき最も右の位置(index)を返す |
insort_left(a, x) |
x を左側に挿入してリスト a を保つ |
insort_right(a, x) または insort(a, x) |
x を右側に挿入してリスト a を保つ |
import bisect
a = [1, 2, 4, 4, 4, 7]
# x=4 を挿入する位置(最左)
print(bisect.bisect_left(a, 4)) # 出力: 2
# x=4 を挿入する位置(最右)
print(bisect.bisect_right(a, 4)) # 出力: 5bisect_left(a, 4)は、4をリストの左端に近い位置に挿入するならどこかを返します(ここでは index 2)。bisect_right(a, 4)は、4を右端に挿入する位置(index 5)を返します。
例えば、「リスト a の中で 3 以上 6 以下の要素の数を数えたい」という場合:
a = [1, 2, 4, 4, 4, 7]
a.sort() # 必ずソート済みであること
left = bisect.bisect_left(a, 3) # 3以上の最初の位置
right = bisect.bisect_right(a, 6) # 6以下の最後の位置 + 1
count = right - left
print(count) # 出力: 3(a[2]〜a[4]が対象)bisectは リストがソート済みであることが前提です。- 時間計算量はすべて O(log n) です(非常に高速)。
- 挿入 (
insort) 操作は要素数が多くなるとやや遅くなることがあります(listは配列型なので挿入時にシフトが必要)。
- 区間クエリ(特定の範囲にある要素を数える)
- 動的に増えるソート済みリストへの追加と検索
- 多数のクエリに対して高速に答える場合(今回の問題など)
| 操作 | 使用関数 | 目的 |
|---|---|---|
| x以上の最初の位置 | bisect_left(a, x) |
下限検索 |
| x以下の最後の位置 | bisect_right(a, x) |
上限検索 |
| xをリストに左側へ挿入 | insort_left(a, x) |
挿入保持 |
| xをリストに右側へ挿入 | insort_right(a, x) |
挿入保持 |