Pythonでボールが入る箱の行と列の位置を効率的に求める方法
2つの配列AとBがあるとします。配列Aのサイズは行数を表し、A[i]はi行目に存在する箱の個数を意味します。一方、配列Bはボールの配列であり、B[i]はボールに書かれた番号を表します。各ボールi(値がB[i])は、先頭から数えてB[i]番目の箱に配置されるものとします。このとき、B[i]のそれぞれに対応する箱の「行」と「列」を求めるのが本記事の目的です。
例えば、入力が A = [3, 4, 5, 6]、B = [1, 3, 5, 2] の場合、出力は [(1, 1), (1, 3), (2, 2), (1, 2)] となります。具体的な対応関係は以下の通りです。
- B[0] = 1 → 1行目・1列目
- B[1] = 3 → 1行目・3列目
- B[2] = 5 → 2行目・2列目
- B[3] = 2 → 1行目・2列目
解決のアプローチ:累積和と二分探索
この問題を効率的に解く鍵となるのは、累積和(prefix sum)と二分探索(binary search)の組み合わせです。配列Aを累積和に変換しておけば、「ある番号のボールが何行目に入るか」を高速に特定できます。手順は以下の通りです。
- len_a を配列Aのサイズ、len_b を配列Bのサイズとします。
- i を 1 から len_a までループし、A[i] = A[i] + A[i-1] として累積和を作成します。
- i を 0 から len_b までループし、以下の処理を行います。
- row := ソート順を保ったままB[i]を挿入できるインデックス(二分探索で取得)
- row >= 1 の場合:box_num := B[i] − A[row − 1]
- それ以外の場合:box_num := B[i]
- (row + 1, box_num) のペアを出力します。
Pythonでの実装例
Pythonでは標準ライブラリの bisect モジュールを使うことで、二分探索を簡単に実装できます。以下がコード例です。
import bisect
def get_position(A, B):
len_a = len(A)
len_b = len(B)
# 配列Aを累積和に変換
for i in range(1, len_a):
A[i] += A[i - 1]
# 各ボールの行と列を求める
for i in range(len_b):
row = bisect.bisect_left(A, B[i])
if row >= 1:
box_num = B[i] - A[row - 1]
else:
box_num = B[i]
print((row + 1, box_num))
A = [3, 4, 5, 6]
B = [1, 3, 5, 2]
get_position(A, B)
入力
[3, 4, 5, 6], [1, 3, 5, 2]
出力
(1, 1) (1, 3) (2, 2) (1, 2)
処理のポイント
累積和変換後の配列Aは [3, 7, 12, 18] となり、各行までの箱の総数を表します。bisect_left により、ボールの番号がどの区間に属するかをO(log n)で判定でき、その区間の開始値との差分が列番号になります。この手法により、ボールごとに全行を走査するO(n×m)の素朴な方法と比べ、大幅な高速化が可能です。
-
Pythonで中継点を経由して現在位置から目的地に到達できるか判定するプログラム
問題の概要 2次元平面上に、座標 (px, py) の地点 p に置かれたポインタがあるとします。このポインタを、別の地点 q(座標 qx, qy)まで移動させたいのですが、ポインタは自由に動けるわけではありません。途中に配置された点をたどることでしか、目的地へは到達できません。 ここで、複数の座標点を格納した配列 paths が与えられます。ポインタは、現在位置から上下左右に1マス離れた点、すなわち (x+1, y)、(x, y+1)、(x−1, y)、(x, y−1) のいずれかに該当する点であれば、そこへ移動できます。 さらに重要なルールとして、paths に含まれる点は必ず配列の先頭か
-
Pythonでグリッド内のボールの着地位置を求めるプログラム
問題概要 m × n のグリッドボックスを考えます。各セルには、左上から右下、もしくは右上から左下へ向けて斜めの板が設置されています。グリッドの上端からボールを落とし、それぞれのボールが底まで到達できるか、そしてどの列に着地するのかを求めるのがこの問題です。 グリッドは行列として与えられ、各セルの値は板の向きを表します。 1: 左上から右下へ下る斜めの板 -1: 右上から左下へ下る斜めの板 n 個のボールを上端の各列から順に落としたとき、底に到達したボールの着地列を答えとして返します。途中で側面の壁に当たったり、V字型の溝にはまって動けなくなったボールについては -1 を出力します。 3