Pythonでクエリから最も近い部屋を見つける効率的なアルゴリズム
問題概要
roomsという配列があるとします。rooms[i]はペア[roomId_i, size_i]を含み、roomId_iは部屋のID、size_iはその部屋のサイズを表します。すべての部屋番号は一意(重複なし)です。
さらに、queriesという別の配列も与えられます。queries[j]はペア[preferred_j, minSize_j]を含みます。j番目のクエリに対する答えは、次の条件を満たす部屋のIDです。
部屋のサイズがminSize_j以上であること
|id − preferred_j| が最小となること
絶対差が同点になった場合は、IDが最も小さい部屋を採用します。条件を満たす部屋が存在しない場合は−1を返します。このとき、queriesと同じ長さのanswer配列を作成し、j番目の要素にj番目のクエリへの回答を格納して返すのが目的です。
具体例で理解する
たとえば、rooms = [[2,2],[1,2],[3,2]]、queries = [[3,1],[3,3],[5,2]] が入力された場合、出力は [3, -1, 3] になります。理由は次のとおりです。
クエリ [3,1] の場合: 部屋3が最も近く(|3 − 3| = 0)、そのサイズ2はminSizeの1以上であるため、答えは3になります。
クエリ [3,3] の場合: サイズが3以上の部屋は存在しないため、答えは−1になります。
クエリ [5,2] の場合: 部屋3が最も近く(|3 − 5| = 2)、そのサイズ2はminSizeの2以上であるため、答えは3になります。
解法のアプローチ
この問題を効率的に解くには、次の手順に従います。
roomsをサイズの昇順でソートします。サイズが同じ場合は部屋IDの昇順で並べます。
queriesを、インデックスiを含むタプル(qid, size, i)のリストに変換します。
queriesをサイズの降順でソートします。サイズが同じ場合はpreferredの降順、それも同じ場合はインデックス順に並べます。
ans := queriesと同じ長さの配列を用意し、すべて−1で初期化します。
X := 新しい空のリストを用意します。
queries内の各(qid, size, i)に対して、以下を実行します。
roomsが空でなく、roomsの末尾要素のサイズがsize以上である間、次を繰り返します。
(idr, p) := roomsの末尾から要素を取り除きます。
idrをXに挿入し、Xがソートされた状態を保ちます(bisect.insortを使用)。
Xが空でない場合:
j := qidを挿入してもXがソートされたままになる挿入位置(bisect.bisectで取得)
jがXの長さと等しい場合 → ans[i] := Xの末尾要素
jが0の場合 → ans[i] := X[0]
それ以外の場合:
X[j] − qid < qid − X[j−1] ならば ans[i] := X[j]
そうでなければ ans[i] := X[j−1]
ansを返します。
この手法のポイントは、クエリをminSizeの降順に処理することで、これまでに処理した部屋(サイズが現在のminSize以上のもの)だけをXに蓄積できる点にあります。こうすることで、各クエリの時点でXには「条件を満たす部屋のID」のみが含まれており、あとは二分探索で最も近いIDを高速に特定できます。
Pythonでの実装例
理解を深めるために、以下の実装例をご覧ください。
import bisect
def solve(rooms, queries):
rooms.sort(key = lambda x: (x[1], x[0]))
queries = [(qid,size,i) for i, (qid, size) in enumerate(queries)]
queries.sort(key = lambda x: (x[1], x[0], x[2]), reverse = True)
ans = [-1] * len(queries)
X = []
for qid, size, i in queries:
while rooms and rooms[-1][1] >= size:
idr, _ = rooms.pop()
bisect.insort(X, idr)
if X:
j = bisect.bisect(X, qid)
if j == len(X):
ans[i] = X[-1]
elif j == 0:
ans[i] = X[0]
else:
if X[j] - qid < qid - X[j-1]:
ans[i] = X[j]
else:
ans[i] = X[j-1]
return ans
rooms = [[2,2],[1,2],[3,2]]
queries = [[3,1],[3,3],[5,2]]
print(solve(rooms, queries))入力
[[2,2],[1,2],[3,2]], [[3,1],[3,3],[5,2]]
出力
[3, -1, 3]
計算量について
部屋とクエリの数をそれぞれn、mとすると、ソートにかかる時間はO(n log n + m log m)です。各部屋は一度だけXに挿入され、挿入・検索はいずれもO(log n)で行えるため、全体の計算量はO((n + m) log(n + m))となります。これは全件を毎回走査する素朴な方法(O(n × m))に比べて大幅に高速であり、大規模な入力にも対応できます。
-
Pythonで2つのソート済み配列から最も近いペアを見つける方法
この記事では、昇順にソートされた2つの配列から「目標値に最も近い合計を持つペア」を見つける問題と、その効率的な解法について詳しく解説します。問題文問題: ソート済みの2つの配列と目標値 x が与えられます。各配列から1つずつ要素を選んで作るペアのうち、その合計が x に最も近くなる組み合わせを見つけてください。解き方のポイント:二ポインタ法すべてのペアを総当たりで調べると計算量は O(m×n) になりますが、配列がソート済みであることを活かせば、二ポインタ法によって O(m+n) まで高速化できます。手順は以下の通りです。片方の配列は先頭から、もう片方の配列は末尾から走査を開始します。現在のペ
-
PythonでリストからN個の最大要素を取得する方法
整数のリストが与えられたとき、その中からN個の大きな要素を取り出して新しいリストとして返すのが、ここでの課題です。本記事では、基本的なループ処理による方法から、Python標準ライブラリを活用した効率的な方法まで、サンプルコードとともに解説します。 例 入力 : [40, 5, 10, 20, 9] N = 2 出力 : [40, 20] アルゴリズム 整数のリストと、取得する要素数Nを受け取ります。 N回のループを実行します。 各ループでリスト内の最大値を探し、新しいリストに格納すると同時に元のリストから削除します。 実装コード def Nnumberele(list1, N):