Pythonで安全な距離を保てる最大のkの値を求めるプログラム
0と1だけで構成された2次元のバイナリ行列を考えてみましょう。ここで「0」は空きセル(誰もいないマス)、「1」は人が存在するセルを表します。2つのセル間の距離は、x座標の差とy座標の差のうち大きい方の値(チェビシェフ距離)として定義されます。ある空きセルから、行列内のすべての人、および行列の4つの辺それぞれへの距離がすべてk以上であるとき、この行列は安全係数kにおいて「安全」であるとみなされます。この記事では、安全性を保証できる最大の係数kの値を求める方法を解説します。
たとえば、入力が以下のような行列だったとしましょう。
| 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 | 0 |
| 0 | 1 | 1 | 1 | 0 |
| 0 | 1 | 1 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 |
この場合、出力は1になります。中央のセルを選べば、グリッド内のすべての人への距離が最低でも1確保できるためです。
解法の考え方
この問題は、動的計画法(DP)を使うことで効率的に解けます。基本的な発想は次のとおりです。
- まず行列の0と1を反転(XOR 1)します。これにより「空きセル」が1に変わり、空き領域だけを扱う問題へと変換できます。
- 反転後の行列にDPを適用し、空きセルのみで構成される「最大の正方形」の一辺の長さを求めます。
- 最大の正方形の一辺の長さが分かれば、その中心に位置することで得られる安全距離、すなわち答えは
(ans + 1) // 2として計算できます。
アルゴリズムの手順
- N := 行列Aの行数
- M := 行列A[0]の列数
- i を 0 から N まで繰り返す:
- j を 0 から M まで繰り返す:
- A[i, j] := A[i, j] XOR 1(0と1を反転)
- j を 0 から M まで繰り返す:
- ans := 0
- i を 0 から N まで繰り返す:
- j を 0 から M まで繰り返す:
- i と j がどちらも0ではなく、かつ A[i, j] が 1 の場合:
- A[i, j] := 1 + min(A[i - 1, j], A[i, j - 1], A[i - 1, j - 1])
- ans := max(A[i, j], ans)
- i と j がどちらも0ではなく、かつ A[i, j] が 1 の場合:
- j を 0 から M まで繰り返す:
- (ans + 1) // 2 を返す
実装例
理解を深めるために、実際のPythonコードを見てみましょう。
class Solution:
def solve(self, A):
N = len(A)
M = len(A[0])
for i in range(N):
for j in range(M):
A[i][j] ^= 1
ans = 0
for i in range(N):
for j in range(M):
if i and j and A[i][j]:
A[i][j] = 1 + min(A[i - 1][j], A[i][j - 1], A[i - 1][j - 1])
ans = max(A[i][j], ans)
return (ans + 1) // 2
ob = Solution()
matrix = [
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0],
]
print(ob.solve(matrix))
入力
[
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0],
]
出力
1
まとめ
このアルゴリズムの計算量はO(N×M)で、行列を2回走査するだけで済むため非常に効率的です。0と1の反転によって「人のいない領域における最大正方形」の問題へ帰着させ、DPで一辺の長さを求め、最後に中心からの距離へ変換する、という流れがポイントです。
-
【Python】各アイテムを何度でも選べるナップサック問題で最大価値を求めるプログラム
問題概要同じ長さを持つ2つのリスト weights(重さ)と values(価値)、および整数 capacity(容量)が与えられます。weights[i] と values[i] は、それぞれ i 番目のアイテムの重さと価値を表します。ここで特別なルールとして、各アイテムは何個でも(何度でも)選んでよいものとします。合計の重さが capacity を超えない範囲でアイテムを選ぶとき、得られる価値の合計の最大値を求めるのがこの問題です。これは「無制限ナップサック問題(Unbounded Knapsack Problem)」として知られる古典的な動的計画法の応用例です。たとえば、次の入力を考えて
-
Pythonで辞書から2番目に大きい値を取得する3つの方法
はじめに この記事では、辞書(ディクショナリ)に格納された値の中から「2番目に大きい値」を取り出す方法を、複数のアプローチに分けてわかりやすく解説します。 問題設定: キーと値を持つ辞書が与えられたとき、その値の中で2番目に大きい値を求めて出力します。 アプローチ1:sorted()関数と負のインデックスを使う方法 まず、sorted()関数で辞書の値を昇順に並べ替え、負のインデックス [-2] を指定することで、後ろから2番目の要素(=2番目に大きい値)を取得します。コードが非常に短くシンプルなのが特徴です。 コード例 # 入力 example_dict = {tutor: 3, tutor