Medium
题目描述
给你一个 m x n 的二进制矩阵 grid 和一个整数 health。
你从左上角 (0, 0) 开始,想要到达右下角 (m - 1, n - 1)。
你可以向上、下、左、右移动到相邻的单元格,但你的健康值必须保持为正数。
单元格 (i, j) 如果 grid[i][j] = 1,则被认为是不安全的,会使你的健康值减少 1。
如果你能以 1 或更多的健康值到达最终单元格,返回 true,否则返回 false。
示例 1:
输入:grid = [[0,1,0,0,0],[0,1,0,1,0],[0,0,0,1,0]], health = 1
输出:true
解释:可以沿着灰色单元格安全到达最终单元格。
示例 2:
输入:grid = [[0,1,1,0,0,0],[1,0,1,0,0,0],[0,1,1,1,0,1],[0,0,1,0,1,0]], health = 3
输出:false
解释:安全到达最终单元格至少需要 4 个健康点。
示例 3:
输入:grid = [[1,1,1],[1,0,1],[1,1,1]], health = 5
输出:true
解释:可以沿着灰色单元格安全到达最终单元格。任何不经过单元格 (1, 1) 的路径都是不安全的,因为到达最终单元格时健康值会降到 0。
约束条件:
- m == grid.length
- n == grid[i].length
- 1 <= m, n <= 50
- 2 <= m * n
- 1 <= health <= m + n
- grid[i][j] 是 0 或 1
提示:
- 使用 01 BFS
解题思路
这是一个在带权图中寻找最短路径的问题。关键是理解每个格子的"权重":安全格子权重为0,危险格子权重为1。我们需要找到从起点到终点的路径,使得总权重不超过 health-1。
解法分析
方法一:01-BFS(推荐) 由于边权只有0和1两种情况,可以使用01-BFS算法。这是一种特殊的BFS,使用双端队列:
- 权重为0的边,将新状态加入队列头部
- 权重为1的边,将新状态加入队列尾部
这样保证了队列中的状态按照距离递增排列,第一次到达终点时就是最优解。
方法二:Dijkstra算法 使用优先队列实现的Dijkstra算法也可以解决,但由于只有两种边权,01-BFS更高效。
方法三:动态规划 可以用DP记录到达每个位置的最小健康消耗,但空间复杂度较高。
实现要点
- 使用双端队列进行01-BFS
- 记录每个位置的最小健康消耗,避免重复访问
- 起始位置如果是危险格子,初始消耗为1
- 当到达终点时,检查剩余健康值是否≥1
代码实现
class Solution {
public:
bool findSafeWalk(vector<vector<int>>& grid, int health) {
int m = grid.size(), n = grid[0].size();
vector<vector<int>> cost(m, vector<int>(n, INT_MAX));
deque<pair<int, int>> dq;
int startCost = grid[0][0];
cost[0][0] = startCost;
dq.push_back({0, 0});
int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
while (!dq.empty()) {
auto [x, y] = dq.front();
dq.pop_front();
for (auto& dir : dirs) {
int nx = x + dir[0], ny = y + dir[1];
if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
int newCost = cost[x][y] + grid[nx][ny];
if (newCost < cost[nx][ny]) {
cost[nx][ny] = newCost;
if (grid[nx][ny] == 0) {
dq.push_front({nx, ny});
} else {
dq.push_back({nx, ny});
}
}
}
}
return health - cost[m-1][n-1] >= 1;
}
};
class Solution:
def findSafeWalk(self, grid: List[List[int]], health: int) -> bool:
from collections import deque
m, n = len(grid), len(grid[0])
cost = [[float('inf')] * n for _ in range(m)]
dq = deque()
start_cost = grid[0][0]
cost[0][0] = start_cost
dq.append((0, 0))
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
while dq:
x, y = dq.popleft()
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < m and 0 <= ny < n:
new_cost = cost[x][y] + grid[nx][ny]
if new_cost < cost[nx][ny]:
cost[nx][ny] = new_cost
if grid[nx][ny] == 0:
dq.appendleft((nx, ny))
else:
dq.append((nx, ny))
return health - cost[m-1][n-1] >= 1
public class Solution {
public bool FindSafeWalk(IList<IList<int>> grid, int health) {
int m = grid.Count, n = grid[0].Count;
int[,] cost = new int[m, n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
cost[i, j] = int.MaxValue;
}
}
var dq = new LinkedList<(int, int)>();
int startCost = grid[0][0];
cost[0, 0] = startCost;
dq.AddLast((0, 0));
int[,] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
while (dq.Count > 0) {
var (x, y) = dq.First.Value;
dq.RemoveFirst();
for (int i = 0; i < 4; i++) {
int nx = x + dirs[i, 0], ny = y + dirs[i, 1];
if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
int newCost = cost[x, y] + grid[nx][ny];
if (newCost < cost[nx, ny]) {
cost[nx, ny] = newCost;
if (grid[nx][ny] == 0) {
dq.AddFirst((nx, ny));
} else {
dq.AddLast((nx, ny));
}
}
}
}
return health - cost[m-1, n-1] >= 1;
}
}
var findSafeWalk = function(grid, health) {
const m = grid.length;
const n = grid[0].length;
const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]];
const pq = [[0, 0, health - grid[0][0]]];
const visited = new Set();
while (pq.length > 0) {
pq.sort((a, b) => b[2] - a[2]);
const [row, col, hp] = pq.shift();
if (hp <= 0) continue;
if (row === m - 1 && col === n - 1) {
return true;
}
const key = `${row},${col},${hp}`;
if (visited.has(key)) continue;
visited.add(key);
for (const [dr, dc] of directions) {
const newRow = row + dr;
const newCol = col + dc;
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n) {
const newHp = hp - grid[newRow][newCol];
const newKey = `${newRow},${newCol},${newHp}`;
if (!visited.has(newKey) && newHp > 0) {
pq.push([newRow, newCol, newHp]);
}
}
}
}
return false;
};
复杂度分析
| 复杂度类型 | 01-BFS解法 |
|---|---|
| 时间复杂度 | O(mn) |
| 空间复杂度 | O(mn) |
说明:
- 时间复杂度:每个格子最多被访问一次,总共mn个格子
- 空间复杂度:需要存储每个位置的最小代价,以及双端队列的空间