持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
每日刷题 2022.06.03
- leetcode原题链接:leetcode.cn/problems/ma…
- 难度:困难
- 方法:bfs
题目
- 你被请来给一个要举办高尔夫比赛的树林砍树。树林由一个 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
解释: 由于中间一行被障碍阻塞,无法访问最下面一行中的树。
提示
- 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的条件:next是1(地面,可以行走)或者大于1(树的高度)的数- 并且 当前所在位置
cur如果是树,还需要当前树的高度小于下一个位置next的树的高度,才能到达下一个位置。
- 最终会计算出从
[0,0]位置开始,往下砍掉的树的最小步数。- 如果最终统计的步数(记为
step)step - 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;
};