Pythonで家から最寄りのメールボックスまでの合計距離を最小化するプログラムの書き方
問題の概要
家の位置を表す配列 houses と整数 k が与えられます。houses[i] は一本の通り沿いにある i 番目の家の位置を示しており、この通りに k 個のメールボックスを設置することを考えます。このとき、各家から最寄りのメールボックスまでの距離の合計が最小になるような設置場所を求めるのが目的です。
たとえば、入力が houses = [6,7,9,16,22]、k = 2 の場合を考えてみましょう。メールボックスを位置 7 と 18 に設置すると、各家からの最短距離の合計は次のように計算できます。
|6−7| + |7−7| + |9−7| + |16−18| + |22−18| = 1 + 0 + 2 + 2 + 4 = 9
したがって、この場合の出力は 9 になります。
解決のアプローチ
この問題は、次の手順で解くことができます。
- リスト
housesを昇順にソートします。 - 関数
util()を定義します。引数として区間の始点idx、終点n、使用できるメールボックスの数kを受け取ります。 kが 1 の場合(残りの家すべてにメールボックスを1つだけ割り当てる場合):- 区間の中央値
core := houses[(n + idx) // 2]をメールボックスの設置位置とします。 - 各区画の家との絶対差
|houses[i] - core|の総和を返します。
- 区間の中央値
- それ以外の場合は
resultを無限大で初期化し、分割位置iをidxからnまで動かしながら再帰的に探索します。n - i < k - 1となったら、残りのメールボックスを配置できなくなるためループを抜けます。resultを「左側の区間に1つのメールボックスを置いた場合」と「右側の区間に残りk-1個を配置した場合」の和の最小値で更新します。
- 最終的に
util(0, len(houses) - 1, k)を返します。
なぜ中央値を使うのか
1つのメールボックスで複数の家をカバーするとき、絶対誤差の合計を最小化する最適な設置位置は中央値です。外れ値の影響を受けにくく、平均値よりも合計距離を小さくできるため、このアルゴリズムでは各区間に対して中央値を採用しています。
実装例
以下のコードを実行して、実際の動作を確認してみましょう。
def solve(houses, k):
houses.sort()
def util(idx, n, k):
if k == 1:
core = houses[(n + idx) // 2]
return sum([abs(houses[i] - core) for i in range(idx, n + 1)])
result = float('inf')
for i in range(idx, n + 1):
if n - i < k - 1:
break
result = min(result, util(idx, i, 1) + util(i+1, n, k - 1))
return result
return util(0, len(houses) - 1, k)
houses = [6,7,9,16,22]
k = 2
print(solve(houses, k))入力
[6,7,9,16,22], 2
出力
9
まとめ
この解法は、区間を再帰的に分割しながら、各区間の最適なメールボックス位置(中央値)を組み合わせて全体の最小コストを求める手法です。家の数やメールボックスの数が増えると計算量が増加するため、実運用ではメモ化などの動的計画法による高速化を検討するとよいでしょう。
-
Pythonで二分木の2つのノード間の距離を求めるプログラム
二分木が与えられたとき、その中の2つのノード間の距離を求めることを考えます。グラフの場合と同じように、2つのノードを結ぶ経路上の辺(エッジ)の数を数え、その本数を距離として返します。 二分木のノード構造 木の各ノードは、次のような構造を持っています。 data : <整数値> right : <木の別のノードへのポインタ> left : <木の別のノードへのポインタ> 問題の例 例として、次のような二分木を考えてみましょう。 この木において、ノード「2」とノード「8」の間の距離を求めたいとします。このときの出力は 4 になります。 ノード2からノード8へ至
-
Pythonで二分木のノードとその子孫の最大絶対差を求めるプログラム
問題概要 二分木が与えられたとき、任意のノードとその子孫との間の絶対差の最大値を求めることを考えます。 例えば、次のような二分木が入力として与えられた場合を考えてみましょう。 この場合、ノード8とノード1の間の差が最も大きくなるため、出力は 7 となります。 解法のアプローチ:DFSを使った追跡 この問題は、DFS(深さ優先探索)を用いることで効率的に解けます。各ノードについて「その部分木内の最小値」と「最大値」を追跡しながら、現在のノードの値との差を順次更新していくのがポイントです。 具体的な手順は以下の通りです。 dfs() 関数を定義します。引数としてノードを受け取ります。 ノード