Pythonで最大長のスネークシーケンスを見つける方法
スネークシーケンスとは
数値が格納された2次元グリッド(マトリックス)が与えられたとき、その中からスネークシーケンスを見つけて返す問題を考えてみましょう。候補が複数存在する場合は、どれか1つを返せば十分です。
スネークシーケンスとは、グリッド内の隣接するセルの数値をつなげて作る列のことです。各セルにおいて、右隣または下隣のセルの値は、現在のセルの値に対して ±1 でなければなりません。つまり、現在位置がセル (a, b) にある場合、右隣のセル (a, b+1) の値が ±1 なら右へ移動でき、下隣のセル (a+1, b) の値が ±1 なら下へ移動できます。
例として使うグリッド
| 10 | 7 | 6 | 3 |
| 9 | 8 | 7 | 6 |
| 8 | 4 | 2 | 7 |
| 2 | 2 | 2 | 8 |
このグリッドの場合、出力は以下のようになります。
- 最大長:6
- シーケンス:10 (0, 0) → 9 (1, 0) → 8 (1, 1) → 7 (1, 2) → 6 (1, 3) → 7 (2, 3) → 8 (3, 3)
解き方(アルゴリズム)
この問題は動的計画法(DP)を使うことで効率よく解けます。lookup テーブルに「各セルを終点とするスネークシーケンスの最大長」を記録していきます。
パス復元関数 get_path()
- 関数
get_path()を定義します。引数は grid、mat、i、j です。 - path := 空のリストを作成します。
- pt := 点 [i, j] を作成し、path の末尾に追加します。
- grid[i][j] が 0 でない間、次を繰り返します。
- i > 0 かつ grid[i][j] − 1 == grid[i-1][j] の場合:pt := [i-1, j] を path に追加し、i := i − 1 とします。
- そうでなく、j > 0 かつ grid[i][j] − 1 == grid[i][j-1] の場合:pt := [i, j-1] を path に追加し、j := j − 1 とします。
- path を返します。
メイン処理
- lookup := M × N サイズの表を作成し、すべて 0 で初期化します。
- length_max := 0、max_row := 0、max_col := 0 とします。
- i を 0 から M-1 まで、j を 0 から N-1 まで走査します(i または j が非ゼロの場合のみ処理)。
- i > 0 かつ |grid[i-1][j] − grid[i][j]| == 1 の場合:lookup[i][j] = max(lookup[i][j], lookup[i-1][j] + 1) と更新し、length_max より大きければ length_max・max_row・max_col を更新します。
- j > 0 かつ |grid[i][j-1] − grid[i][j]| == 1 の場合:同様に lookup[i][j] = max(lookup[i][j], lookup[i][j-1] + 1) と更新し、必要なら最大値情報を更新します。
- length_max を表示します。
- path := get_path(lookup, grid, max_row, max_col) でパスを復元し、逆順に出力します。
実装例
それでは、理解を深めるために実際の実装を見てみましょう。
M = 4
N = 4
def get_path(grid, mat, i, j):
path = list()
pt = [i, j]
path.append(pt)
while (grid[i][j] != 0):
if (i > 0 and grid[i][j]-1 == grid[i-1][j]):
pt = [i-1, j]
path.append(pt)
i -= 1
elif (j > 0 and grid[i][j]-1 == grid[i][j-1]):
pt = [i, j-1]
path.append(pt)
j -= 1
return path
def get_sequence(grid):
lookup = [[0 for i in range(N)] for j in range(M)]
length_max = 0
max_row = 0
max_col = 0
for i in range(M):
for j in range(N):
if (i or j):
if (i > 0 and abs(grid[i-1][j] - grid[i][j]) == 1):
lookup[i][j] = max(lookup[i][j], lookup[i-1][j] + 1)
if (length_max < lookup[i][j]):
length_max = lookup[i][j]
max_row = i
max_col = j
if (j > 0 and abs(grid[i][j-1] - grid[i][j]) == 1):
lookup[i][j] = max(lookup[i][j], lookup[i][j-1] + 1)
if (length_max < lookup[i][j]):
length_max = lookup[i][j]
max_row = i
max_col = j
print("Maximum length:", length_max)
path = get_path(lookup, grid, max_row, max_col)
print("Sequence is:")
for ele in reversed(path):
print(grid[ele[0]][ele[1]], " [", ele[0], ", ", ele[1], "]", sep="")
grid = [
[10, 7, 6, 3],
[9, 8, 7, 6],
[8, 4, 2, 7],
[2, 2, 2, 8]]
get_sequence(grid)入力
[[10, 7, 6, 3], [9, 8, 7, 6], [8, 4, 2, 7], [2, 2, 2, 8]]
出力
Maximum length: 6 Sequence is: 10 [0, 0] 9 [1, 0] 8 [1, 1] 7 [1, 2] 6 [1, 3] 7 [2, 3] 8 [3, 3]
時間計算量は O(M×N)、空間計算量も O(M×N) であり、全セルを一度だけ走査して DP 表を埋めるため、大きなグリッドでも効率的に動作します。
-
Pythonで同じ長さのリボンをk本切り出せる最大の長さを求めるプログラム
正の整数のリスト(各リボンの長さを表します)と整数 k が与えられます。リボンは何回でも切ることができるので、長さ r のリボンをちょうど k 本作れるような最大の r を求めてください。そのような解が存在しない場合は -1 を返します。たとえば、入力が ribbons = [1, 2, 5, 7, 15]、k = 5 の場合、出力は 5 になります。長さ 15 のリボンを長さ 5 の 3 本に切り分け、長さ 7 のリボンは長さ 2 と 5 に切り分けます。さらに長さ 5 のリボンがもう 1 本あるため、合計で長さ 5 のリボンが 5 本手に入ります。解法のアプローチ:二分探索この問題は二分探
-
Pythonで制約付きの建物の最大高さを求めるプログラム
問題の概要整数 n と制約リスト restrictions が与えられたとします。私たちは都市に n 棟の新しい建物を一列に建てようとしていますが、高さに関するいくつかの制限があります。建物には左から順に 1 から n までの番号が付けられており、各制約は restrictions[i] = (id_i, max_height_i) の形式で表され、「id_i 番の建物の高さは max_height_i 以下でなければならない」ことを意味します。建物の高さに関する都市の規則は以下のとおりです。各建物の高さは 0 以上でなければなりません。1 番の建物(最初の建物)の高さは必ず 0 です。隣接す