Hard

题目描述

你被要求砍掉森林中的所有树木来为高尔夫赛事做准备。森林用一个 m x n 的矩阵表示,在这个矩阵中:

  • 0 表示该格子不能通过。
  • 1 表示一个空的可以通过的格子。
  • 大于 1 的数字表示格子里有一棵可以通过的树,这个数字是树的高度。

在一步中,你可以朝任何四个方向走:北、东、南、西。如果你站在一个有树的格子里,你可以选择砍掉它。

你必须按从矮到高的顺序砍掉树木。当你砍掉一棵树时,该格子的值会变为 1(一个空格子)。

从点 (0, 0) 开始,返回砍掉所有树木所需的最少步数。如果不能砍掉所有树木,返回 -1。

注意:输入保证没有两棵树有相同的高度,并且至少有一棵树需要砍掉。

示例 1:

输入:forest = [[1,2,3],[0,0,4],[7,6,5]]
输出:6
解释:按照上面的路径,你可以用 6 步按从矮到高的顺序砍掉所有树木。

示例 2:

输入:forest = [[1,2,3],[0,0,0],[7,6,5]]
输出:-1
解释:由于中间一行被阻挡,无法访问底部行的树木。

示例 3:

输入:forest = [[2,3,4],[0,0,5],[8,7,6]]
输出:6
解释:你可以按照示例 1 的相同路径砍掉所有树木。
注意你可以在不走任何步数的情况下砍掉位于 (0, 0) 的第一棵树。

限制条件:

  • m == forest.length
  • n == forest[i].length
  • 1 <= m, n <= 50
  • 0 <= forest[i][j] <= 10^9
  • 所有树的高度都不相同。

解题思路

这是一个典型的图论问题,需要结合排序和 BFS 来解决。

核心思路:

  1. 首先收集所有树木的位置和高度,并按高度从小到大排序
  2. 从起点 (0,0) 开始,依次走到每棵树的位置并砍掉
  3. 每次移动都使用 BFS 寻找从当前位置到目标位置的最短路径
  4. 累加所有移动步数作为最终答案

详细步骤:

  • 遍历矩阵,将所有大于 1 的树木信息(高度、行、列)存入数组并排序
  • 设置当前位置为起点 (0,0),总步数为 0
  • 对于排序后的每棵树,使用 BFS 计算从当前位置到该树位置的最短距离
  • 如果无法到达某棵树,返回 -1
  • 累加距离到总步数,更新当前位置为该树的位置
  • 所有树砍完后返回总步数

BFS 实现要点:

  • 使用队列存储待访问的位置
  • 使用访问标记避免重复访问
  • 四个方向进行扩展:上下左右
  • 遇到障碍物(值为 0)或越界时跳过

时间复杂度主要由树木数量和 BFS 搜索决定,空间复杂度由 BFS 队列和访问标记决定。

代码实现

class Solution {
public:
    int cutOffTree(vector<vector<int>>& forest) {
        int m = forest.size(), n = forest[0].size();
        
        // 收集所有树木并按高度排序
        vector<vector<int>> trees;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (forest[i][j] > 1) {
                    trees.push_back({forest[i][j], i, j});
                }
            }
        }
        sort(trees.begin(), trees.end());
        
        // 从起点到目标点的最短距离
        auto bfs = [&](int sr, int sc, int tr, int tc) -> int {
            if (sr == tr && sc == tc) return 0;
            
            queue<pair<int, int>> q;
            vector<vector<bool>> visited(m, vector<bool>(n, false));
            q.push({sr, sc});
            visited[sr][sc] = true;
            
            int steps = 0;
            int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
            
            while (!q.empty()) {
                int size = q.size();
                steps++;
                for (int i = 0; i < size; i++) {
                    auto [r, c] = q.front();
                    q.pop();
                    
                    for (int j = 0; j < 4; j++) {
                        int nr = r + dirs[j][0];
                        int nc = c + dirs[j][1];
                        
                        if (nr >= 0 && nr < m && nc >= 0 && nc < n && 
                            !visited[nr][nc] && forest[nr][nc] != 0) {
                            if (nr == tr && nc == tc) return steps;
                            visited[nr][nc] = true;
                            q.push({nr, nc});
                        }
                    }
                }
            }
            return -1;
        };
        
        int sr = 0, sc = 0, totalSteps = 0;
        
        for (auto& tree : trees) {
            int steps = bfs(sr, sc, tree[1], tree[2]);
            if (steps == -1) return -1;
            totalSteps += steps;
            sr = tree[1];
            sc = tree[2];
        }
        
        return totalSteps;
    }
};
class Solution:
    def cutOffTree(self, forest: List[List[int]]) -> int:
        m, n = len(forest), len(forest[0])
        
        # 收集所有树木并按高度排序
        trees = []
        for i in range(m):
            for j in range(n):
                if forest[i][j] > 1:
                    trees.append((forest[i][j], i, j))
        trees.sort()
        
        def bfs(sr, sc, tr, tc):
            if sr == tr and sc == tc:
                return 0
            
            queue = collections.deque([(sr, sc)])
            visited = set()
            visited.add((sr, sc))
            steps = 0
            
            while queue:
                steps += 1
                for _ in range(len(queue)):
                    r, c = queue.popleft()
                    
                    for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
                        nr, nc = r + dr, c + dc
                        
                        if (0 <= nr < m and 0 <= nc < n and 
                            (nr, nc) not in visited and forest[nr][nc] != 0):
                            if nr == tr and nc == tc:
                                return steps
                            visited.add((nr, nc))
                            queue.append((nr, nc))
            
            return -1
        
        sr, sc = 0, 0
        total_steps = 0
        
        for _, tr, tc in trees:
            steps = bfs(sr, sc, tr, tc)
            if steps == -1:
                return -1
            total_steps += steps
            sr, sc = tr, tc
        
        return total_steps
public class Solution {
    public int CutOffTree(IList<IList<int>> forest) {
        int m = forest.Count, n = forest[0].Count;
        
        // 收集所有树木并按高度排序
        var trees = new List<(int height, int row, int col)>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (forest[i][j] > 1) {
                    trees.Add((forest[i][j], i, j));
                }
            }
        }
        trees.Sort();
        
        int BFS(int sr, int sc, int tr, int tc) {
            if (sr == tr && sc == tc) return 0;
            
            var queue = new Queue<(int, int)>();
            var visited = new HashSet<(int, int)>();
            queue.Enqueue((sr, sc));
            visited.Add((sr, sc));
            
            int steps = 0;
            int[,] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
            
            while (queue.Count > 0) {
                int size = queue.Count;
                steps++;
                for (int i = 0; i < size; i++) {
                    var (r, c) = queue.Dequeue();
                    
                    for (int j = 0; j < 4; j++) {
                        int nr = r + dirs[j, 0];
                        int nc = c + dirs[j, 1];
                        
                        if (nr >= 0 && nr < m && nc >= 0 && nc < n && 
                            !visited.Contains((nr, nc)) && forest[nr][nc] != 0) {
                            if (nr == tr && nc == tc) return steps;
                            visited.Add((nr, nc));
                            queue.Enqueue((nr, nc));
                        }
                    }
                }
            }
            return -1;
        }
        
        int sr = 0, sc = 0, totalSteps = 0;
        
        foreach (var (_, tr, tc) in trees) {
            int steps = BFS(sr, sc, tr, tc);
            if (steps == -1) return -1;
            totalSteps += steps;
            sr = tr;
            sc = tc;
        }
        
        return totalSteps;
    }
}
var cutOffTree = function(forest) {
    const m = forest.length;
    const n = forest[0].length;
    
    // Collect all trees and sort by height
    const trees = [];
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (forest[i][j] > 1) {
                trees.push([forest[i][j], i, j]);
            }
        }
    }
    trees.sort((a, b) => a[0] - b[0]);
    
    // BFS to find shortest path between two points
    const bfs = (startRow, startCol, endRow, endCol) => {
        if (startRow === endRow && startCol === endCol) return 0;
        
        const queue = [[startRow, startCol, 0]];
        const visited = new Set();
        visited.add(`${startRow},${startCol}`);
        const dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]];
        
        while (queue.length > 0) {
            const [row, col, steps] = queue.shift();
            
            for (const [dr, dc] of dirs) {
                const newRow = row + dr;
                const newCol = col + dc;
                const key = `${newRow},${newCol}`;
                
                if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n &&
                    forest[newRow][newCol] !== 0 && !visited.has(key)) {
                    
                    if (newRow === endRow && newCol === endCol) {
                        return steps + 1;
                    }
                    
                    visited.add(key);
                    queue.push([newRow, newCol, steps + 1]);
                }
            }
        }
        return -1;
    };
    
    let totalSteps = 0;
    let currentRow = 0, currentCol = 0;
    
    // Cut trees in order
    for (const [height, treeRow, treeCol] of trees) {
        const steps = bfs(currentRow, currentCol, treeRow, treeCol);
        if (steps === -1) return -1;
        
        totalSteps += steps;
        currentRow = treeRow;
        currentCol = treeCol;
        forest[treeRow][treeCol] = 1; // Cut the tree
    }
    
    return totalSteps;
};

复杂度分析

复杂度类型说明
时间复杂度O(T × M × N)T 为树木数量,每次 BFS 最坏需要遍历整个矩阵
空间复杂度O(M × N)BFS 队列和访问标记数组的空间开销