2850-将石头分散到网格图的最少移动次数

Raphael Liu Lv10

给你一个大小为 3 * 3 ,下标从 0 开始的二维整数矩阵 grid ,分别表示每一个格子里石头的数目。网格图中总共恰好有 9
个石头,一个格子里可能会有 多个 石头。

每一次操作中,你可以将一个石头从它当前所在格子移动到一个至少有一条公共边的相邻格子。

请你返回每个格子恰好有一个石头的 最少移动次数

示例 1:

**输入:** grid = [[1,1,0],[1,1,1],[1,2,1]]
**输出:** 3
**解释:** 让每个格子都有一个石头的一个操作序列为:
1 - 将一个石头从格子 (2,1) 移动到 (2,2) 。
2 - 将一个石头从格子 (2,2) 移动到 (1,2) 。
3 - 将一个石头从格子 (1,2) 移动到 (0,2) 。
总共需要 3 次操作让每个格子都有一个石头。
让每个格子都有一个石头的最少操作次数为 3 。

示例 2:

**输入:** grid = [[1,3,0],[1,0,0],[1,0,3]]
**输出:** 4
**解释:** 让每个格子都有一个石头的一个操作序列为:
1 - 将一个石头从格子 (0,1) 移动到 (0,2) 。
2 - 将一个石头从格子 (0,1) 移动到 (1,1) 。
3 - 将一个石头从格子 (2,2) 移动到 (1,2) 。
4 - 将一个石头从格子 (2,2) 移动到 (2,1) 。
总共需要 4 次操作让每个格子都有一个石头。
让每个格子都有一个石头的最少操作次数为 4 。

提示:

  • grid.length == grid[i].length == 3
  • 0 <= grid[i][j] <= 9
  • grid 中元素之和为 9

请看 视频讲解 第三题。

方法一:枚举全排列

由于所有移走的石子个数等于所有移入的石子个数(即 0 的个数),我们可以把移走的石子的坐标记录到列表 from 中(可能有重复的坐标),移入的石子的坐标记录到列表 to 中。这两个列表的长度是一样的。

枚举 from 的所有排列,与 to 匹配,即累加从 from}[i] 到 to}[i] 的曼哈顿距离。

所有距离之和的最小值就是答案。

[sol-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution:
def minimumMoves(self, grid: List[List[int]]) -> int:
from_ = []
to = []
for i, row in enumerate(grid):
for j, cnt in enumerate(row):
if cnt > 1:
from_.extend([(i, j)] * (cnt - 1))
elif cnt == 0:
to.append((i, j))

ans = inf
for from2 in permutations(from_):
total = 0
for (x1, y1), (x2, y2) in zip(from2, to):
total += abs(x1 - x2) + abs(y1 - y2)
ans = min(ans, total)
return ans
[sol-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Solution {
public int minimumMoves(int[][] grid) {
List<int[]> from = new ArrayList<>();
List<int[]> to = new ArrayList<>();
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
if (grid[i][j] > 1) {
for (int k = 1; k < grid[i][j]; k++) {
from.add(new int[]{i, j});
}
} else if (grid[i][j] == 0) {
to.add(new int[]{i, j});
}
}
}

int ans = Integer.MAX_VALUE;
for (List<int[]> from2 : permutations(from)) {
int total = 0;
for (int i = 0; i < from2.size(); i++) {
int[] f = from2.get(i);
int[] t = to.get(i);
total += Math.abs(f[0] - t[0]) + Math.abs(f[1] - t[1]);
}
ans = Math.min(ans, total);
}
return ans;
}

private List<List<int[]>> permutations(List<int[]> arr) {
List<List<int[]>> result = new ArrayList<>();
permute(arr, 0, result);
return result;
}

private void permute(List<int[]> arr, int start, List<List<int[]>> result) {
if (start == arr.size()) {
result.add(new ArrayList<>(arr));
}
for (int i = start; i < arr.size(); i++) {
swap(arr, start, i);
permute(arr, start + 1, result);
swap(arr, start, i);
}
}

private void swap(List<int[]> arr, int i, int j) {
int[] temp = arr.get(i);
arr.set(i, arr.get(j));
arr.set(j, temp);
}
}
[sol-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public:
int minimumMoves(vector<vector<int>> &grid) {
vector<pair<int, int>> from, to;
for (int i = 0; i < grid.size(); ++i) {
for (int j = 0; j < grid[i].size(); ++j) {
if (grid[i][j]) {
for (int k = 1; k < grid[i][j]; k++) {
from.emplace_back(i, j);
}
} else {
to.emplace_back(i, j);
}
}
}

int ans = INT_MAX;
do {
int total = 0;
for (int i = 0; i < from.size(); ++i) {
total += abs(from[i].first - to[i].first) + abs(from[i].second - to[i].second);
}
ans = min(ans, total);
} while (next_permutation(from.begin(), from.end()));
return ans;
}
};
[sol-Go]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
type pair struct{ x, y int }

func minimumMoves(grid [][]int) int {
var from, to []pair
for i, row := range grid {
for j, cnt := range row {
if cnt > 1 {
for k := 1; k < cnt; k++ {
from = append(from, pair{i, j})
}
} else if cnt == 0 {
to = append(to, pair{i, j})
}
}
}

ans := math.MaxInt
permute(from, 0, func() {
total := 0
for i, f := range from {
total += abs(f.x-to[i].x) + abs(f.y-to[i].y)
}
ans = min(ans, total)
})
return ans
}

func permute(a []pair, i int, do func()) {
if i == len(a) {
do()
return
}
permute(a, i+1, do)
for j := i + 1; j < len(a); j++ {
a[i], a[j] = a[j], a[i]
permute(a, i+1, do)
a[i], a[j] = a[j], a[i]
}
}

func abs(x int) int { if x < 0 { return -x }; return x }
func min(a, b int) int { if b < a { return b }; return a }
[sol-JavaScript]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var minimumMoves = function(grid) {
const from = [];
const to = [];
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
if (grid[i][j] > 1) {
for (let k = 1; k < grid[i][j]; k++) {
from.push([i, j]);
}
} else if (grid[i][j] === 0) {
to.push([i, j]);
}
}
}

let ans = Infinity;
for (let from2 of permute(from)) {
let total = 0;
for (let i = 0; i < from2.length; i++) {
total += Math.abs(from2[i][0] - to[i][0]) + Math.abs(from2[i][1] - to[i][1]);
}
ans = Math.min(ans, total);
}
return ans;
};

function permute(arr) {
const result = [];
perm(arr, 0, result);
return result;
}

function perm(arr, start, result) {
if (start === arr.length) {
result.push([...arr]);
}
for (let i = start; i < arr.length; i++) {
[arr[start], arr[i]] = [arr[i], arr[start]];
perm(arr, start + 1, result);
[arr[start], arr[i]] = [arr[i], arr[start]];
}
}

复杂度分析

  • 时间复杂度:\mathcal{O}(mn\cdot(mn)!)。其中 m=3,n=3。
  • 空间复杂度:\mathcal{O}(mn)。

方法二:最小费用最大流

更快的做法是最小费用最大流。即使是 10\times 10 的网格也可以做。

建图规则如下:

  • 从每个大于 1 的格子向每个等于 0 的格子连边,容量为 1,费用为两个格子之间的曼哈顿距离。
  • 从超级源点向每个大于 1 的格子连边,容量为格子的值减一(即移走的石子数),费用为 0。
  • 从每个等于 0 的格子向超级汇点连边,容量 1(即移入的石子数),费用为 0。

答案为最大流时,对应的最小费用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
func minimumMoves(grid [][]int) int {
m, n := len(grid), len(grid[0])
src := m * n // 超级源点
dst := src + 1 // 超级汇点
type edge struct{ to, rid, cap, cost int }
g := make([][]edge, m*n+2)
addEdge := func(from, to, cap, cost int) {
g[from] = append(g[from], edge{to, len(g[to]), cap, cost})
g[to] = append(g[to], edge{from, len(g[from]) - 1, 0, -cost})
}
for x, row := range grid {
for y, v := range row {
if v > 1 {
addEdge(src, x*n+y, v-1, 0)
for i, r := range grid {
for j, w := range r {
if w == 0 {
addEdge(x*n+y, i*n+j, 1, abs(x-i)+abs(y-j))
}
}
}
} else if v == 0 {
addEdge(x*n+y, dst, 1, 0)
}
}
}

// 下面是最小费用最大流模板
const inf int = 1e9
dist := make([]int, len(g))
type vi struct{ v, i int }
fa := make([]vi, len(g))
spfa := func() bool {
for i := range dist {
dist[i] = 1e9
}
dist[src] = 0
inQ := make([]bool, len(g))
inQ[src] = true
q := []int{src}
for len(q) > 0 {
v := q[0]
q = q[1:]
inQ[v] = false
for i, e := range g[v] {
if e.cap == 0 {
continue
}
w := e.to
if newD := dist[v] + e.cost; newD < dist[w] {
dist[w] = newD
fa[w] = vi{v, i}
if !inQ[w] {
q = append(q, w)
inQ[w] = true
}
}
}
}
return dist[dst] < inf
}
ek := func() (maxFlow, minCost int) {
for spfa() {
// 沿 st-end 的最短路尽量增广
minF := inf
for v := dst; v != src; {
p := fa[v]
if c := g[p.v][p.i].cap; c < minF {
minF = c
}
v = p.v
}
for v := dst; v != src; {
p := fa[v]
e := &g[p.v][p.i]
e.cap -= minF
g[v][e.rid].cap += minF
v = p.v
}
maxFlow += minF
minCost += dist[dst] * minF
}
return
}
_, cost := ek()
return cost
}

func abs(x int) int { if x < 0 { return -x }; return x }

相似题目

下面是一些涉及到「匹配」的题目:

 Comments
On this page
2850-将石头分散到网格图的最少移动次数