文章目录
题目
标题和出处
标题:为高尔夫比赛砍树
难度
6 级
题目描述
要求
你被请来给一个要举办高尔夫比赛的森林砍树。森林由一个 m × n \texttt{m} \times \texttt{n} m×n 的矩阵表示。在这个矩阵中:
- 0 \texttt{0} 0 表示障碍,不能行走。
- 1 \texttt{1} 1 表示地面,可以行走。
- 比 1 \texttt{1} 1 大的数表示有树的单元格,可以行走,数值表示树的高度。
每一步,你都可以向水平或竖直的四个方向之一移动一个单位,如果你站的地方有一棵树,那么你可以决定是否要砍倒它。
你需要按照树的高度从低向高砍掉所有的树,每砍过一颗树,该单元格的值变为 1 \texttt{1} 1(即变为地面)。
从 (0, 0) \texttt{(0, 0)} (0, 0) 点开始工作,返回你砍完所有树需要走的最小步数。如果你无法砍完所有的树,返回 -1 \texttt{-1} -1。
保证没有两棵树的高度是相同的,并且至少需要砍倒一棵树。
示例
示例 1:

输入: forest = [[1,2,3],[0,0,4],[7,6,5]] \texttt{forest = [[1,2,3],[0,0,4],[7,6,5]]} forest = [[1,2,3],[0,0,4],[7,6,5]]
输出: 6 \texttt{6} 6
解释:沿着上面的路径,可以用 6 \texttt{6} 6 步,按从最矮到最高的顺序砍掉这些树。
示例 2:

输入: forest = [[1,2,3],[0,0,0],[7,6,5]] \texttt{forest = [[1,2,3],[0,0,0],[7,6,5]]} forest = [[1,2,3],[0,0,0],[7,6,5]]
输出: -1 \texttt{-1} -1
解释:由于中间一行被障碍阻塞,无法访问最下面一行中的树。
示例 3:
输入: forest = [[2,3,4],[0,0,5],[8,7,6]] \texttt{forest = [[2,3,4],[0,0,5],[8,7,6]]} forest = [[2,3,4],[0,0,5],[8,7,6]]
输出: 6 \texttt{6} 6
解释:可以按与示例 1 相同的路径来砍掉所有的树。注意 (0,0) \texttt{(0,0)} (0,0) 位置的树可以直接砍去,不用算步数。
数据范围
- m = forest.length \texttt{m} = \texttt{forest.length} m=forest.length
- n = forest[i].length \texttt{n} = \texttt{forest[i].length} n=forest[i].length
- 1 ≤ m, n ≤ 50 \texttt{1} \le \texttt{m, n} \le \texttt{50} 1≤m, n≤50
- 0 ≤ forest[i][j] ≤ 10 9 \texttt{0} \le \texttt{forest[i][j]} \le \texttt{10}^\texttt{9} 0≤forest[i][j]≤109
- 树的高度各不相同
解法
思路和算法
这道题要求按照树的高度从低向高砍掉所有的树,因此需要首先得到所有树的位置并按照树的高度排序,然后按照顺序依次计算砍每棵树需要走的最小步数。如果砍每棵树的步数最小,则总步数也最小。
为了计算两个位置之间的最小步数,可以使用广度优先搜索实现。初始时位于 ( 0 , 0 ) (0, 0) (0,0),按照树的高度递增的顺序依次遍历每棵树,使用广度优先搜索计算从上一个位置到当前树所在位置的最小步数。由于矩阵中的元素 0 0 0 表示不能行走的障碍,因此广度优先搜索的过程中需要注意不能走到元素 0 0 0 的位置。对于每棵树,判断是否可以到达,执行相应的操作。
-
如果不能到达,则返回 − 1 -1 −1。
-
如果可以到达,则将当前步数加到总步数。
如果要砍的第一棵树位于 ( 0 , 0 ) (0, 0) (0,0),则砍第一棵树的最小步数是 0 0 0。
如果所有的树都能到达,则返回总步数。
代码
java
class Solution {
static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int m, n;
List<List<Integer>> forest;
public int cutOffTree(List<List<Integer>> forest) {
this.m = forest.size();
this.n = forest.get(0).size();
this.forest = forest;
List<int[]> trees = new ArrayList<int[]>();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (forest.get(i).get(j) > 1) {
trees.add(new int[]{i, j});
}
}
}
Collections.sort(trees, (a, b) -> forest.get(a[0]).get(a[1]) - forest.get(b[0]).get(b[1]));
int totalSteps = 0;
int[] start = {0, 0};
int size = trees.size();
for (int i = 0; i < size; i++) {
int[] tree = trees.get(i);
int steps = bfs(start, tree);
if (steps < 0) {
return -1;
}
totalSteps += steps;
start = tree;
}
return totalSteps;
}
public int bfs(int[] source, int[] target) {
if (Arrays.equals(source, target)) {
return 0;
}
boolean[][] visited = new boolean[m][n];
visited[source[0]][source[1]] = true;
Queue<int[]> queue = new ArrayDeque<int[]>();
queue.offer(source);
int steps = 0;
while (!queue.isEmpty()) {
steps++;
int size = queue.size();
for (int i = 0; i < size; i++) {
int[] cell = queue.poll();
int row = cell[0], col = cell[1];
for (int[] dir : dirs) {
int newRow = row + dir[0], newCol = col + dir[1];
if (newRow == target[0] && newCol == target[1]) {
return steps;
}
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n && forest.get(newRow).get(newCol) > 0 && !visited[newRow][newCol]) {
visited[newRow][newCol] = true;
queue.offer(new int[]{newRow, newCol});
}
}
}
}
return -1;
}
}
复杂度分析
-
时间复杂度: O ( m 2 n 2 ) O(m^2n^2) O(m2n2),其中 m m m 和 n n n 分别是矩阵 forest \textit{forest} forest 的行数和列数。需要首先遍历矩阵获得所有树的位置,并将树的位置按照树的高度排序,然后按照树的高度递增的顺序依次使用广度优先搜索计算两棵树之间的最小步数,遍历矩阵需要 O ( m n ) O(mn) O(mn) 的时间,由于最多有 m n mn mn 棵树,因此排序需要 O ( m n log ( m n ) ) O(mn \log (mn)) O(mnlog(mn)) 的时间,广度优先搜索的次数是 O ( m n ) O(mn) O(mn) 因此需要 O ( m 2 n 2 ) O(m^2n^2) O(m2n2) 的时间,总时间复杂度是 O ( m 2 n 2 ) O(m^2n^2) O(m2n2)。
-
空间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 和 n n n 分别是矩阵 forest \textit{forest} forest 的行数和列数。存储树的位置的列表需要 O ( m n ) O(mn) O(mn) 的空间,广度优先搜索使用的标记数组和队列需要 O ( m n ) O(mn) O(mn) 的空间。