📅  最后修改于: 2023-12-03 15:39:19.418000             🧑  作者: Mango
给定一个 $n \times m$ 的矩阵,每个单元格里有若干个硬币。你需要把每个单元格的硬币移动到矩阵里的任何一个单元格,每次可以选择一个单元格,把其上的若干个硬币移动到相邻的四个单元格中的任意一个中。求移动所有硬币所需的最少步数。
首先观察题目,这明显是一个搜索问题。我们可以从每个格子开始搜索,每次可以向相邻的四个格子移动一些硬币,直到所有的硬币都被移走。
这样做的问题在于,搜索的空间非常大。对于 $n$ 行 $m$ 列的矩阵,总共有 $nm$ 个起点,每个起点最多可以往 $4^{nm}$ 个方向搜索,时间复杂度为 $O(4^{nm})$,显然是不可接受的。
那么怎样优化这个算法呢?我们可以从另一个角度思考:如果两个格子之间的硬币数目是相等的,那么从其中一个格子移动到另一个格子是没有意义的。
因此,我们可以先计算出每个格子的硬币数目 $c_{i,j}$,然后统计出每个数目 $k$ 的格子数 $s_k$,并计算出它们的平均值 $avg$。
对于每个格子 $(i,j)$,令 $d_{i,j}$ 表示将其硬币移动到数目为 $k$ 的格子所需的最小步数,则有
$$ d_{i,j} = |c_{i,j} - k| $$
也就是说,对于每个格子,我们只需要知道它的硬币数目和平均值就可以计算出该格子到目标格子的最短距离。这个问题可以使用 BFS 解决。具体来说,从所有数目为 $k$ 的格子开始 BFS,将所有格子的最短距离更新到 $d_{i,j}$ 中。
def min_moves(matrix):
n, m = len(matrix), len(matrix[0])
cnt = [0] * (n * m + 1)
total = 0
for i in range(n):
for j in range(m):
cnt[matrix[i][j]] += 1
total += matrix[i][j]
avg = total // (n * m)
def bfs(k):
q = [(i, j) for i in range(n) for j in range(m) if matrix[i][j] == k]
vis = [[False] * m for _ in range(n)]
dist = [[float('inf')] * m for _ in range(n)]
for i, j in q:
vis[i][j] = True
dist[i][j] = 0
while q:
x, y = q.pop(0)
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
nx, ny = x + dx, y + dy
if nx < 0 or nx >= n or ny < 0 or ny >= m or vis[nx][ny]:
continue
vis[nx][ny] = True
dist[nx][ny] = dist[x][y] + 1
q.append((nx, ny))
for i in range(n):
for j in range(m):
if matrix[i][j] != k:
d[i][j] += dist[i][j]
d = [[0] * m for _ in range(n)]
for k in range(len(cnt)):
if cnt[k] == 0:
continue
if cnt[k] == n * m:
return 0
if cnt[k] <= avg:
bfs(k)
else:
bfs(k - 1)
bfs(k)
return min([d[i][j] for i in range(n) for j in range(m)])
代码中使用了一个辅助数组 cnt
,用于计算每个数目的硬币在矩阵中出现的次数,以及一个变量 total
,用于计算所有硬币的总数。根据这些数据,可以计算出平均值 avg
。
使用 BFS 计算每个格子到目标格子的最短距离时,可以选择从数目为 $k$ 的格子或 $k-1$ 的格子开始 BFS。根据题意,如果当前格子的硬币数目小于等于平均值,那么目标格子应该是数目为 $k$ 的格子;否则目标格子应该是数目为 $k$ 和 $k-1$ 的格子。在 BFS 结束后,可以将每个格子的最短距离加到 d
数组中,最后可以遍历 d
数组,找到其中的最小值作为答案。
这个题目的思路非常巧妙,通过对搜索空间的优化,将时间复杂度降低到了 $O(nm)$。在实际工程中,我们也需要经常从多个角度思考问题,寻找出最优的解决方案。