[ BFS ] 675. 为高尔夫比赛砍树

154 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

每日刷题 2022.06.03

题目

  • 你被请来给一个要举办高尔夫比赛的树林砍树。树林由一个 m x n 的矩阵表示, 在这个矩阵中:
    • 0 表示障碍,无法触碰
    • 1 表示地面,可以行走
  • 比 1 大的数 表示有树的单元格,可以行走,数值表示树的高度
  • 每一步,你都可以向上、下、左、右四个方向之一移动一个单位,如果你站的地方有一棵树,那么你可以决定是否要砍倒它。
  • 你需要按照树的高度从低向高砍掉所有的树,每砍过一颗树,该单元格的值变为 1(即变为地面)。
  • 你将从 (0, 0) 点开始工作,返回你砍完所有树需要走的最小步数。 如果你无法砍完所有的树,返回 -1 。
  • 可以保证的是,没有两棵树的高度是相同的,并且你至少需要砍倒一棵树。

示例

  • 示例1 image.png
输入: forest = [[1,2,3],[0,0,4],[7,6,5]]
输出: 6
解释: 沿着上面的路径,你可以用 6 步,按从最矮到最高的顺序砍掉这些树。
  • 示例2 image.png
输入: forest = [[1,2,3],[0,0,0],[7,6,5]]
输出: -1
解释: 由于中间一行被障碍阻塞,无法访问最下面一行中的树。

提示

  • m == forest.length
  • n == forest[i].length
  • 1 <= m, n <= 50
  • 0 <= forest[i][j] <= 109

解题思路(大家请看这个)

  • 分析:只有0是不可以走的,其他的都可以行走,如果当前节点是树的高度,那么你可以选择砍还是不砍。

正确的题意(大家请看这个)

  • 题目限制🚫了只能从低到高来砍树🪵,并且不会存在相同高度的树,这样就避免了先砍两个相同高度的树,哪个的问题。从此处也可以分析出:那么需要每次砍的树的顺序是一定的。
  • 也就是说:需要在所给的forest数组中从树的高度低的依次往树的高度高的进行砍,那么这个顺序可以通过给forest数组中所有树🌲的高度进行排序。
  • 排序后,问题就可以简化为:每次需要计算:从上一个节点到下一个节点所需的最小步数(且两个节点都是已知的)
  • 最后,只需要将找到的所有两两节点之间的最小步数相加,就会得到从低到高砍掉所有树的最小步数
  • 需要注意:[0,0]节点,不论是什么,树的高度还是地面、障碍物,都能从当前节点走到下一个节点的。也就是说:开始节点即使是0障碍物,也不会被阻止不能前行,因为题目中说了,可以保证,至少需要砍到一棵树。
  • 再回头想想,的确,这样的理解才更符合题意,因为题目中说了“你站的地方有一棵树,那么你可以决定是否要砍倒它。”最开始的时候,理解是从上一个节点的上下左右到下一个节点(中间最多差1步),砍不砍倒取决于能不能从上一个节点过去;现在正确的理解:在这个树的节点上,砍不砍倒,取决于其是不是我们现在要找的两个节点最短路径上的节点
  • 最终,因为可以砍到的树🌲,都会变成地面1,因此只需要遍历最终的forest数组中是否存在> 1的情况(存在树的情况),如果不存在,则返回step;否则返回-1

错误理解分析(自己记录📝,大家可以直接看正确的题意和代码即可)

  • 最开始的时候,理解错了题目的含义:从[0,0]点开始,当前位置cur每次可以向上下左右四个方向走一步。那么走到下一个位置next的条件:
    • next1(地面,可以行走)或者大于1(树的高度)的数
    • 并且 当前所在位置cur如果是树,还需要当前树的高度小于下一个位置next的树的高度,才能到达下一个位置。
  • 最终会计算出从[0,0]位置开始,往下砍掉的树的最小步数。
    • 如果最终统计的步数(记为stepstep - 1 < 小于树的总数,那么就返回-1;如果大于就返回step步数。
    • step - 1 是因为要删除第一个节点的步数; 同样统计树的总数的时候,也会不计算[0,0]是否是树。
    • 因为是依次查找附近的上下左右的每一个节点,每次step都是+1,因此最后遍历完成,只需要和树🌲的总数进行比较,查看所有的树是否全部都被遍历到,如果能够都被遍历到,那么就返回step,如果不行就返回-1,说明有0障碍物阻止,无法将所有的树全部砍完。

BFS书写心得

  • bfs中判断一个节点,是否是目标节点,那么最好是在每次从出队的元素queue.shift()的时候,再进行判断;不要直接在当前层循环添加下一层的元素的时候,进行判断。因为此时的层数不对,处理起来较为复杂。

AC代码(大家请看这个)

  • BFS最短路径
/**
 * @param {number[][]} forest
 * @return {number}
 */
var cutOffTree = function(forest) {
  // 可以向4个方向进行移动
  // 从低向高砍,砍到后变为1,变为地面
  // 返回:砍完所有树需要走的最小步数,如果没有办法全部,返回-1
  // 至少砍一棵树
  // (0,0)位置的树可以直接砍掉,不需要计算步数
  // 最小步数:bfs
  const f = forest, n = f.length, m = f[0].length;
  let visited = new Array(n).fill(0).map(() => new Array(m).fill(0));
  let nx = [-1,1,0,0],ny = [0,0,1,-1],step = 0;
  // 排序,实现从小到大的排序
  let ff = new Array(n * m), idx = 0;
  for(let i = 0; i < n; i++) {
    for(let j = 0; j < m; j++) {
      if(f[i][j] > 1) ff[idx++] = f[i][j];
    }
  }
  //找到最小值和最小的下标
  // console.log('id:', id, minn)
  // 排序好的砍树路径
  ff.sort((a, b) => a - b);
  const fn = ff.length;
  let queue = [[0,0]], index = 0, ans = 0;
  visited[0][0] = 1;
  while(queue.length != 0) {
    if(index == fn) break;
    let q = queue.length;
    for(let i = 0;i < q; i++) {
      // 遍历这一层的,步数相同的位置
      let t = queue.shift(), curX = t[0], curY = t[1];
      // 在这里判断当前这一层的是否有符合条件的
      if(f[curX][curY] == ff[index]) {
        // 找到,就进行下一次
        // console.log('enter')
        // 砍树,变为平地
        f[curX][curY] = 1;
        // 加上这次成功的步数
        ans += step;
        // console.log('enter abs :', curX, curY, ans,step)
        // 需要寻找下一个目标
        step = -1;
        index++;
        queue.length = 0;
        queue.push([curX,curY]);
        visited = new Array(n).fill(0).map(() => new Array(m).fill(0))
        // flag = true;
        break;
      }
      for(let j = 0; j < 4; j++) {
        // 如果当前的高度小于后面的高度,或者此地为1,那么就可以通过
        let x = curX + nx[j], y = curY + ny[j];
        // console.log('xxyyy::', x,y,j)
        if(x < n && x >= 0 && y < m && y >= 0 && !visited[x][y] && f[x][y] != 0){
          // 满足条件的,可以作为下一层
          visited[x][y] = 1;
          // console.log('sdbif',f[x][y], ff[index], x, y);
          queue.push([x,y]);
        }
      }
    }
    step++;
  }
  // 判断数组中的树是否全部为1(除0外)
  for(let i = 0; i < n; i++) {
    for(let j = 0; j < m; j++) {
      if(f[i][j] != 1 && f[i][j] != 0) return -1;
    }
  }
  // 返回最终的步数总和
  return ans;
};