1 介绍
本博客用来记录灵神力扣题单之网格图。
2 训练
2.3 BFS(求最短路)
题目29:909. 蛇梯棋
解题思路:注意审题。st
数组只更新下一个结点的值,经过的梯子入口不算。
C++代码如下,
class Solution {
public:
int snakesAndLadders(vector<vector<int>>& board) {
int n = board.size();
vector<bool> st(n*n+1, false);
vector<vector<int>> grid(n, vector<int>(n, -1));
int flag = 1;
int idx = 1;
for (int i = n-1; i >= 0; --i) {
if (flag > 0) {
for (int j = 0; j < n; ++j) {
grid[i][j] = idx++;
}
} else {
for (int j = n-1; j >= 0; --j) {
grid[i][j] = idx++;
}
}
flag ^= 1;
}
unordered_map<int,int> map_x_y;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (board[i][j] != -1) {
map_x_y[grid[i][j]] = board[i][j];
}
}
}
//计算答案
int level = 0;
queue<int> q;
q.push(1);
st[1] = true;
while (!q.empty()) {
int qsize = q.size();
while (qsize--) {
int i = q.front();
if (i == n*n) { //返回答案
return level;
}
q.pop();
for (int k = 1; k < 7; ++k) {
int ni = i + k;
if (ni > n*n) continue;
if (map_x_y.find(ni) != map_x_y.end()) {
ni = map_x_y[ni];
}
if (st[ni]) continue;
q.push(ni);
st[ni] = true;
}
}
level += 1;
}
return -1;
}
};
python3代码如下,
class Solution:
def snakesAndLadders(self, board: List[List[int]]) -> int:
n = len(board)
grid = [[-1] * n for _ in range(n)]
idx = 1
flag = True
for i in range(n-1,-1,-1):
if flag:
for j in range(n):
grid[i][j] = idx
idx += 1
else:
for j in range(n-1,-1,-1):
grid[i][j] = idx
idx += 1
flag = (flag == False) #更新flag
map_val1_val2 = collections.defaultdict(int)
for i in range(n):
for j in range(n):
if board[i][j] != -1:
map_val1_val2[grid[i][j]] = board[i][j]
st = [False] * (n * n + 1)
q = collections.deque([])
q.append(1)
st[1] = True
level = 0
while len(q) > 0:
tot = len(q)
while tot > 0:
tot -= 1
i = q.popleft()
if i == n*n: #提前判断
return level
for k in range(1,7):
ni = i + k
if ni > n * n:
continue
if ni in map_val1_val2:
ni = map_val1_val2[ni] #更新下一个结点
#下一个结点只能有一个
if st[ni]:
continue
q.append(ni)
st[ni] = True
level += 1
return -1
题目30:1210. 穿过迷宫的最少移动次数
解题思路:状态多一维,表示水平方向或者竖直方向。bfs。
C++代码如下,
class Solution {
public:
int minimumMoves(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
vector<vector<vector<bool>>> st(n, vector<vector<bool>>(m, vector<bool>(2, false)));
int dirs[3][3] = {{1,0,0},{0,1,0},{0,0,1}};
//特判
if (grid[0][0] == 1 || grid[0][1] == 1 || grid[n-1][n-2] == 1 || grid[n-1][n-1] == 1) {
return -1;
}
//蛇尾(i,j),s=0表示水平方向,s=1表示竖直方向
//则蛇头(i+s,j+1-s)
queue<vector<int>> q;
q.push({0,0,0});
st[0][0][0] = true;
int level = 0;
while (!q.empty()) {
int qsize = q.size();
while (qsize--) {
auto t = q.front();
q.pop();
int i = t[0];
int j = t[1];
int s = t[2];
if (i == n-1 && j == n-2 && s == 0) { //返回答案
return level;
}
for (int k = 0; k < 3; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
int ns = s ^ dirs[k][2];
if (ni+ns < 0 || ni+ns >= n || nj+1-ns < 0 || nj+1-ns >= m) continue;
if (st[ni][nj][ns]) continue;
if (grid[ni][nj] == 1 || grid[ni+ns][nj+1-ns] == 1) continue;
if (k == 2 && grid[i+1][j+1] == 1) continue; //操作编号为2时,需要额外判断
q.push({ni,nj,ns});
st[ni][nj][ns] = true;
}
}
level += 1;
}
return -1;
}
};
python3代码如下,
class Solution:
def minimumMoves(self, grid: List[List[int]]) -> int:
n, m = len(grid), len(grid[0])
st = [[[False] * 2 for _ in range(m)] for _ in range(n)]
dirs = [[1,0,0], [0,1,0], [0,0,1]]
#(i,j,s)其中s=0表示水平方向,s=1表示竖直方向
#蛇尾(i,j),则蛇头(i+s,j+1-s)
#特判
if grid[0][0] == 1 or grid[0][1] == 1 or grid[n-1][n-2] == 1 or grid[n-1][n-1] == 1:
return -1
q = collections.deque([])
q.append((0,0,0))
st[0][0][0] = True
level = 0
while len(q) > 0:
qsize = len(q)
while qsize > 0:
qsize -= 1
i,j,s = q.popleft()
if i == n-1 and j == n-2 and s == 0: #返回答案
return level
for k in range(3):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
ns = s ^ dirs[k][2]
if ni+ns < 0 or ni+ns >= n or nj+1-ns < 0 or nj+1-ns >= m:
continue
if st[ni][nj][ns]:
continue
if grid[ni][nj] == 1 or grid[ni+ns][nj+1-ns] == 1:
continue
if k == 2 and grid[i+1][j+1] == 1: #操作编号k=2时,需要额外判断
continue
q.append([ni,nj,ns])
st[ni][nj][ns] = True
level += 1
return -1
题目31:675. 为高尔夫比赛砍树
解题思路:每两个点之间做一次bfs。
C++代码如下,
class Solution {
public:
int cutOffTree(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
if (grid[0][0] == 0) { //特判
return -1;
}
vector<int> nums;
unordered_map<int,pair<int,int>> map_num_xy;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (grid[i][j] > 1) {
nums.push_back(grid[i][j]);
map_num_xy[grid[i][j]] = make_pair(i,j);
}
}
}
sort(nums.begin(), nums.end());
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
function<int(pair<int,int>,pair<int,int>)> compute =[&] (pair<int,int> snode, pair<int,int> enode) -> int {
vector<vector<bool>> st(n, vector<bool>(m, false));
queue<pair<int,int>> q;
q.push(snode);
st[snode.first][snode.second] = true;
int level = 0;
while (!q.empty()) {
int qsize = q.size();
while (qsize--) {
auto [i,j] = q.front();
q.pop();
if (i == enode.first && j == enode.second) { //返回答案
return level;
}
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (st[ni][nj]) continue;
if (grid[ni][nj] == 0) continue;
q.push(make_pair(ni,nj));
st[ni][nj] = true;
}
}
level += 1;
}
return -1;
};
pair<int,int> snode(0,0);
int res = 0;
for (int num : nums) {
pair<int,int> enode = map_num_xy[num];
int ans = compute(snode,enode);
if (ans == -1) {
return -1;
}
res += ans;
snode = enode; //更新snode
}
return res;
}
};
python3代码如下,
class Solution:
def cutOffTree(self, grid: List[List[int]]) -> int:
n, m = len(grid), len(grid[0])
if grid[0][0] == 0: #特判
return -1
nums = []
map_val_xy = collections.defaultdict(list)
for i in range(n):
for j in range(m):
if grid[i][j] > 1:
nums.append(grid[i][j])
map_val_xy[grid[i][j]] = [i,j]
nums.sort()
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
def compute(snode: list, enode: list) -> int:
st = [[False] * m for _ in range(n)]
q = collections.deque([])
q.append(snode)
st[snode[0]][snode[1]] = True
level = 0
while len(q) > 0:
qsize = len(q)
while qsize > 0:
qsize -= 1
i, j = q.popleft()
if i == enode[0] and j == enode[1]: #返回答案
return level
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if st[ni][nj]:
continue
if grid[ni][nj] == 0:
continue
q.append([ni,nj])
st[ni][nj] = True
level += 1
return -1
res = 0
snode = [0,0]
for i in range(len(nums)):
enode = map_val_xy[nums[i]]
ans = compute(snode,enode)
if ans == -1:
return -1
res += ans
snode = enode #更新snode
return res
题目32:749. 隔离病毒
解题思路:考虑一层一层的元素。
C++代码如下,
class Solution {
private:
vector<vector<int>> grid;
int n;
int m;
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
public:
vector<vector<pair<int,int>>> get_vec_coords() {
vector<vector<pair<int,int>>> res;
vector<vector<bool>> st(n, vector<bool>(m, false));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (grid[i][j] == 1 && !st[i][j]) {
vector<pair<int,int>> coords;
queue<pair<int,int>> q;
q.push(make_pair(i,j));
st[i][j] = true;
coords.push_back(make_pair(i,j));
while (!q.empty()) {
auto [x,y] = q.front();
q.pop();
for (int k = 0; k < 4; ++k) {
int nx = x + dirs[k][0];
int ny = y + dirs[k][1];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if (st[nx][ny]) continue;
if (grid[nx][ny] != 1) continue;
q.push(make_pair(nx,ny));
st[nx][ny] = true;
coords.push_back(make_pair(nx,ny));
}
}
res.push_back(coords);
}
}
}
return res;
}
pair<int,int> compute(vector<pair<int,int>> coords) {
int harm = 0;
int door = 0;
vector<vector<bool>> st(n, vector<bool>(m, false));
for (auto [i,j] : coords) {
st[i][j] = true;
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (grid[ni][nj] != 0) continue;
door += 1;
if (st[ni][nj]) continue;
harm += 1;
st[ni][nj] = true;
}
}
return make_pair(harm, door);
}
void update(vector<pair<int,int>>& coords) {
//更新grid
vector<vector<bool>> st(n, vector<bool>(m, false));
for (auto [i,j] : coords) {
st[i][j] = true;
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (grid[ni][nj] != 0) continue;
if (st[ni][nj]) continue;
st[ni][nj] = true;
grid[ni][nj] = 1; //更新grid
}
}
return;
}
int containVirus(vector<vector<int>>& grid) {
this->grid = grid;
this->n = grid.size();
this->m = grid[0].size();
vector<vector<pair<int,int>>> vec_coords = get_vec_coords(); //从grid中获取vec_coords
//cout << "vec_coords.size() = " << vec_coords.size() << endl;
int res = 0;
while (vec_coords.size() > 0) {
int target_i = 0;
auto [target_harm, target_door] = compute(vec_coords[0]);
for (int i = 1; i < vec_coords.size(); ++i) {
auto [curr_harm, curr_door] = compute(vec_coords[i]);
if (curr_harm > target_harm) {
target_i = i;
target_harm = curr_harm;
target_door = curr_door;
}
}
if (target_harm == 0) { //提前返回
return res;
}
//找到了需要处理的病毒集团,它的下标为target_i
res += target_door;
for (auto [i,j] : vec_coords[target_i]) {
this->grid[i][j] = -1; //更新this->grid
}
for (int i = 0; i < vec_coords.size(); ++i) {
if (i == target_i) continue;
update(vec_coords[i]); //更新grid
}
vec_coords = get_vec_coords(); //更新vec_coords
}
return res;
}
};
python3代码如下,
class Solution:
def containVirus(self, grid: List[List[int]]) -> int:
n, m = len(grid), len(grid[0])
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
def bfs(i: int, j: int, st: list) -> list:
res = []
q = collections.deque([])
q.append([i,j])
st[i][j] = True
res.append([i,j])
while len(q) > 0:
i, j = q.popleft()
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if st[ni][nj]:
continue
if grid[ni][nj] != 1:
continue
q.append([ni,nj])
st[ni][nj] = True
res.append([ni,nj])
return res
def get_vec_coords() -> list:
vec_coords = []
st = [[False] * m for _ in range(n)]
for i in range(n):
for j in range(m):
if grid[i][j] == 1 and not st[i][j]:
coords = bfs(i,j, st)
vec_coords.append(coords)
return vec_coords
def compute(coords: list) -> int:
st = [[False] * m for _ in range(n)]
harm = 0
door = 0
for i,j in coords:
st[i][j] = True
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if grid[ni][nj] != 0:
continue
door += 1
if st[ni][nj]:
continue
st[ni][nj] = True
harm += 1
return harm,door
def update(coords: list) -> None:
st = [[False] * m for _ in range(n)]
for i,j in coords:
st[i][j] = True
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if grid[ni][nj] != 0:
continue
if st[ni][nj]:
continue
st[ni][nj] = True
grid[ni][nj] = 1 #把grid中的相应位置进行更新
return
#计算答案
res = 0
vec_coords = get_vec_coords()
while len(vec_coords) > 0:
#且该感染区域对未感染区域的威胁最大且 保证唯一
target_i = 0
target_harm, target_door = compute(vec_coords[0])
for i in range(1,len(vec_coords)):
curr_harm, curr_door = compute(vec_coords[i])
if curr_harm > target_harm:
target_harm = curr_harm
target_i = i
target_door = curr_door
if target_harm == 0: #提前返回
return res
#target_i被控制
res += target_door
#更新vec_coords
for i,j in vec_coords[target_i]:
grid[i][j] = -1 #grid[i][j]=-1表示原先是1,但现在用防火墙控制住了,设置为-1
for i in range(len(vec_coords)):
if i == target_i:
continue
update(vec_coords[i])
vec_coords = get_vec_coords() #根据grid获取vec_coords
return res
2.4 01BFS(边权只有0和1)
解题思路:边权只有0和1的图,可以考虑使用01bfs算法,它的时间复杂度是的,比dijkstra算法要快。遇到边权为0的,在队头插入;遇到边权为1的,在队尾插入。
C++代码如下,
class Solution {
public:
int minCost(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
int dirs[4][2] = {{0,1},{0,-1},{1,0},{-1,0}};
vector<vector<int>> d(n, vector<int>(m, n+m));
vector<vector<bool>> st(n, vector<bool>(m, false));
deque<pair<int,int>> dq;
dq.push_back(make_pair(0,0));
d[0][0] = 0;
while (!dq.empty()) {
auto [i,j] = dq.front();
dq.pop_front();
if (st[i][j]) continue;
st[i][j] = true;
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
int t = grid[i][j] == k+1 ? 0 : 1;
if (d[ni][nj] > d[i][j] + t) {//求最小值
d[ni][nj] = d[i][j] + t;
if (t == 0) {
dq.push_front(make_pair(ni,nj)); //边权为0,往队头插入
} else {
dq.push_back(make_pair(ni,nj)); //边权为1,往队尾插入
}
}
}
}
return d[n-1][m-1];
}
};
python3代码如下,
class Solution:
def minCost(self, grid: List[List[int]]) -> int:
#边权只有0或1可以使用0-1bfs
#遇到边权为0的,在队头插入;遇到边权为1的,在队尾插入
n, m = len(grid), len(grid[0])
dirs = [[0,1],[0,-1],[1,0],[-1,0]] #k=0,往右走;k=1,往左走。。。
d = [[inf] * m for _ in range(n)]
st = [[False] * m for _ in range(n)]
q = collections.deque([])
q.append([0,0])
d[0][0] = 0
while len(q) > 0:
i,j = q.popleft()
if st[i][j]:
continue
st[i][j] = True
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if d[ni][nj] > d[i][j] + int(grid[i][j] != k+1):
d[ni][nj] = d[i][j] + int(grid[i][j] != k+1)
if grid[i][j] != k+1:
q.append([ni,nj])
else:
q.appendleft([ni,nj])
return d[n-1][m-1]
解题思路:01bfs。
C++代码如下,
class Solution {
public:
int minimumObstacles(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
vector<vector<int>> d(n, vector<int>(m, n+m));
vector<vector<bool>> st(n, vector<bool>(m, false));
deque<pair<int,int>> dq;
dq.push_back(make_pair(0,0));
d[0][0] = 0;
while (!dq.empty()) {
auto [i,j] = dq.front();
dq.pop_front();
if (st[i][j]) continue;
st[i][j] = true;
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
int edge = grid[ni][nj];
if (d[ni][nj] > d[i][j] + edge) {
d[ni][nj] = d[i][j] + edge;
if (edge == 1) dq.push_back(make_pair(ni,nj));
else dq.push_front(make_pair(ni,nj));
}
}
}
return d[n-1][m-1];
}
};
python3代码如下,
class Solution:
def minimumObstacles(self, grid: List[List[int]]) -> int:
n, m = len(grid), len(grid[0])
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
st = [[False] * m for _ in range(n)]
d = [[inf] * m for _ in range(n)]
q = collections.deque([])
q.append([0,0])
d[0][0] = 0
while len(q) > 0:
i,j = q.popleft()
if st[i][j]:
continue
st[i][j] = True
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
edge = grid[ni][nj]
if d[ni][nj] > d[i][j] + edge:
d[ni][nj] = d[i][j] + edge
if edge == 1:
q.append([ni,nj])
elif edge == 0:
q.appendleft([ni,nj])
return d[n-1][m-1]
题目35:3286. 穿越网格图的安全路径
解题思路:01bfs。
C++代码如下,
class Solution {
public:
bool findSafeWalk(vector<vector<int>>& grid, int health) {
int n = grid.size();
int m = grid[0].size();
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
vector<vector<int>> d(n, vector<int>(m, n+m));
vector<vector<bool>> st(n, vector<bool>(m, false));
deque<pair<int,int>> dq;
dq.push_back(make_pair(0,0));
d[0][0] = grid[0][0];
while (!dq.empty()) {
auto [i,j] = dq.front();
dq.pop_front();
if (st[i][j]) continue;
st[i][j] = true;
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
int edge = grid[ni][nj];
if (d[ni][nj] > d[i][j] + edge) {
d[ni][nj] = d[i][j] + edge;
if (edge == 1) dq.push_back(make_pair(ni,nj));
else dq.push_front(make_pair(ni,nj));
}
}
}
return health > d[n-1][m-1];
}
};
python3代码如下,
class Solution:
def findSafeWalk(self, grid: List[List[int]], health: int) -> bool:
n, m = len(grid), len(grid[0])
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
d = [[inf] * m for _ in range(n)]
st = [[False] * m for _ in range(n)]
dq = collections.deque([])
dq.append([0,0])
d[0][0] = grid[0][0]
while len(dq) > 0:
i,j = dq.popleft()
if st[i][j]:
continue
st[i][j] = True
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
edge = grid[ni][nj]
if d[ni][nj] > d[i][j] + edge:
d[ni][nj] = d[i][j] + edge
if edge == 1:
dq.append([ni,nj])
else:
dq.appendleft([ni,nj])
return health > d[n-1][m-1]
题目36:1824. 最少侧跳次数
解题思路:01bfs。或者动态规划。
C++代码如下,
class Solution {
public:
int minSideJumps(vector<int>& obstacles) {
int n = obstacles.size();
int m = 3;
int dirs[5][2] = {{1,0},{0,-1},{0,-2},{0,1},{0,2}};
vector<vector<int>> d(n, vector<int>(m, n+m));
vector<vector<bool>> st(n, vector<bool>(m, false));
deque<pair<int,int>> dq;
dq.push_back(make_pair(0,1));
d[0][1] = 0;
while (!dq.empty()) {
auto [i,j] = dq.front();
dq.pop_front();
if (st[i][j]) continue;
st[i][j] = true;
for (int k = 0; k < 5; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (obstacles[ni]-1 == nj) continue;
int edge = k == 0 ? 0 : 1;
if (d[ni][nj] > d[i][j] + edge) {
d[ni][nj] = d[i][j] + edge;
if (edge == 1) {
dq.push_back(make_pair(ni,nj));
} else {
dq.push_front(make_pair(ni,nj));
}
}
}
}
int res = min(d[n-1][0],d[n-1][1]);
res = min(res, d[n-1][2]);
return res;
}
};
python3代码如下,
class Solution:
def minSideJumps(self, obstacles: List[int]) -> int:
#动态规划可以做这道题
#01bfs也可以做这道题
n = len(obstacles)
m = 3
dirs = [[1,0],[0,-1],[0,-2],[0,1],[0,2]]
d = [[inf] * m for _ in range(n)]
st = [[False] * m for _ in range(n)]
q = collections.deque([])
q.append([0,1])
d[0][1] = 0
while len(q) > 0:
i, j = q.popleft()
if st[i][j]:
continue
st[i][j] = True
for k in range(5):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if obstacles[ni]-1 == nj:
continue
edge = 0 if k == 0 else 1
if d[ni][nj] > d[i][j] + edge:
d[ni][nj] = d[i][j] + edge
if edge == 1:
q.append([ni,nj])
else:
q.appendleft([ni,nj])
res = min(d[n-1][0], d[n-1][1], d[n-1][2])
return res
题目37:LCP 56. 信物传送
解题思路:01bfs。
C++代码如下,
class Solution {
public:
int conveyorBelt(vector<string>& grid, vector<int>& start, vector<int>& end) {
int n = grid.size();
int m = grid[0].size();
vector<vector<int>> d(n, vector<int>(m, n+m));
vector<vector<bool>> st(n, vector<bool>(m, false));
deque<pair<int,int>> dq;
dq.push_back(make_pair(start[0],start[1]));
d[start[0]][start[1]] = 0;
int dirs[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
unordered_map<char,int> map_c_k = {{'^',0},{'v',1},{'<',2},{'>',3}};
while (!dq.empty()) {
auto [i,j] = dq.front();
dq.pop_front();
if (st[i][j]) continue;
st[i][j] = true;
int target_k = map_c_k[grid[i][j]];
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
int edge = k == target_k ? 0 : 1;
if (d[ni][nj] > d[i][j] + edge) {
d[ni][nj] = d[i][j] + edge;
if (edge == 1) {
dq.push_back(make_pair(ni,nj));
} else {
dq.push_front(make_pair(ni,nj));
}
}
}
}
return d[end[0]][end[1]];
}
};
python3代码如下,
class Solution:
def conveyorBelt(self, grid: List[str], start: List[int], end: List[int]) -> int:
n, m = len(grid), len(grid[0])
d = [[n+m] * m for _ in range(n)]
st = [[False] * m for _ in range(n)]
q = collections.deque([])
q.append(start)
d[start[0]][start[1]] = 0
dirs = [[-1,0],[1,0],[0,-1],[0,1]]
map_c_k = {'^':0, 'v':1, '<':2, '>':3}
while len(q) > 0:
i, j = q.popleft()
if st[i][j]:
continue
st[i][j] = True
target_k = map_c_k[grid[i][j]]
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
edge = 0 if k == target_k else 1
if d[ni][nj] > d[i][j] + edge:
d[ni][nj] = d[i][j] + edge
if edge == 1:
q.append([ni,nj])
else:
q.appendleft([ni,nj])
return d[end[0]][end[1]]
2.5 综合应用
题目38:1631. 最小体力消耗路径
解题思路:二分+bfs。
C++代码如下,
class Solution {
public:
int minimumEffortPath(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
int left = 0;
int min_x = grid[0][0];
int max_x = grid[0][0];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
min_x = min(min_x, grid[i][j]);
max_x = max(max_x, grid[i][j]);
}
}
int right = max_x - min_x;
int res = -1;
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
function<bool(int)> check =[&] (int mid) -> bool {
vector<vector<bool>> st(n, vector<bool>(m, false));
queue<pair<int,int>> q;
q.push(make_pair(0,0));
st[0][0] = true;
while (!q.empty()) {
auto [i,j] = q.front();
if (i == n-1 && j == m-1) { //提前返回
return true;
}
q.pop();
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (st[ni][nj]) continue;
if (abs(grid[ni][nj]-grid[i][j]) > mid) continue;
q.push(make_pair(ni,nj));
st[ni][nj] = true;
}
}
return false;
};
while (left <= right) {
int mid = (left + right) / 2;
if (check(mid)) {
//求最小值
res = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return res;
}
};
python3代码如下,
class Solution:
def minimumEffortPath(self, grid: List[List[int]]) -> int:
n, m = len(grid), len(grid[0])
left = 0
minx = grid[0][0]
maxx = grid[0][0]
for i in range(n):
for j in range(m):
x = grid[i][j]
minx = min(minx, x)
maxx = max(maxx, x)
right = maxx - minx
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
def check(mid: int) -> bool:
q = collections.deque([])
q.append([0,0])
st = [[False] * m for _ in range(n)]
st[0][0] = True
while len(q) > 0:
i, j = q.popleft()
if i == n-1 and j == m-1:
return True
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if st[ni][nj]:
continue
if abs(grid[ni][nj]-grid[i][j]) > mid:
continue
q.append([ni,nj])
st[ni][nj] = True
return False
res = -1
while left <= right:
mid = (left + right) // 2
if check(mid):
#求最小值
res = mid
right = mid - 1
else:
left = mid + 1
return res
题目39:778. 水位上升的泳池中游泳
解题思路:二分+bfs。
C++代码如下,
class Solution {
public:
int swimInWater(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
int left = grid[0][0];
int max_x = grid[0][0];
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
max_x = max(max_x, grid[i][j]);
}
}
int right = max_x;
int res = -1;
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
function<bool(int)> check =[&] (int mid) -> bool {
vector<vector<bool>> st(n, vector<bool>(m, false));
queue<pair<int,int>> q;
q.push(make_pair(0,0));
st[0][0] = true;
while (!q.empty()) {
auto [i,j] = q.front();
if (i == n-1 && j == m-1) { //提前返回
return true;
}
q.pop();
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (st[ni][nj]) continue;
if (grid[ni][nj] > mid) continue;
q.push(make_pair(ni,nj));
st[ni][nj] = true;
}
}
return false;
};
while (left <= right) {
int mid = (left + right) / 2;
if (check(mid)) {
//求最小值
res = mid;
right = mid-1;
} else {
left = mid + 1;
}
}
return res;
}
};
python3代码如下,
class Solution:
def swimInWater(self, grid: List[List[int]]) -> int:
n, m = len(grid), len(grid[0])
max_x = grid[0][0]
for i in range(n):
for j in range(m):
max_x = max(max_x, grid[i][j])
left = grid[0][0]
right = max_x
res = -1
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
def check(mid: int) -> bool:
st = [[False] * m for _ in range(n)]
q = collections.deque([])
q.append([0,0])
st[0][0] = True
while len(q) > 0:
i, j = q.popleft()
if i == n-1 and j == m-1: #提前返回
return True
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if st[ni][nj]:
continue
if grid[ni][nj] > mid:
continue
q.append([ni,nj])
st[ni][nj] = True
return False
while left <= right:
mid = (left + right) // 2
if check(mid):
#求最小值
res = mid
right = mid - 1
else:
left = mid + 1
return res
题目40:329. 矩阵中的最长递增路径
解题思路:动态规划,计算状态x
时,要提前计算能够转移到它的状态。
C++代码如下,
class Solution {
public:
int longestIncreasingPath(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
vector<vector<int>> coords;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
vector<int> t = {grid[i][j],i,j};
coords.push_back(t);
}
}
sort(coords.begin(), coords.end());
//计算答案
vector<vector<int>> f(n, vector<int>(m, 1)); //状态f[i][j]表示以(i,j)结尾的最长递增序列的长度
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
int res = 1;
for (auto coord : coords) {
int i = coord[1];
int j = coord[2];
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (grid[i][j] >= grid[ni][nj]) continue;
f[ni][nj] = max(f[ni][nj], f[i][j] + 1);
res = max(res, f[ni][nj]);
}
}
return res;
}
};
python3代码如下,
class Solution:
def longestIncreasingPath(self, grid: List[List[int]]) -> int:
#严格递增路径
n, m = len(grid), len(grid[0])
coords = []
for i in range(n):
for j in range(m):
coords.append([grid[i][j],i,j])
coords.sort()
f = [[1] * m for _ in range(n)] #f[i][j]表示以(i,j)为终点的最长递增路径长度
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
res = 1
for _,i,j in coords:
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if grid[i][j] >= grid[ni][nj]:
continue
f[ni][nj] = max(f[ni][nj], f[i][j] + 1)
res = max(res, f[ni][nj])
return res
题目41:1036. 逃离大迷宫
解题思路:
(1)从起点进行bfs
,它能访问到结点的数量大于MAX
。并且,从终点进行bfs
,它能访问到结点的数量大于MAX
。此时,返回True
。否则,返回False
。
(2)还有一种情况,即便能够访问到的结点的数量小于等于MAX
,也返回True
。即,起点能访问到终点。
(3)n = blocked.size()
, 有n个障碍物,利用边界,它所能围成的最大面积为(n-1)*n//2
。故MAX=(n-1)*n//2
。
C++代码如下,
class Solution {
public:
bool isEscapePossible(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {
int n = 1e6;
int t = blocked.size();
const int MAX = (t-1)*t/2;
set<pair<int,int>> blocked_set;
for (auto t : blocked) {
blocked_set.insert(make_pair(t[0],t[1]));
}
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
function<bool(vector<int>,vector<int>)> check =[&](vector<int> snode, vector<int> enode) -> bool {
set<pair<int,int>> visited;
queue<pair<int,int>> q;
q.push(make_pair(snode[0],snode[1]));
visited.insert(make_pair(snode[0],snode[1]));
while (!q.empty() && visited.size() <= MAX) {
auto [i,j] = q.front();
if (i == enode[0] && j == enode[1]) return true; //特判
q.pop();
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= n) continue;
pair<int,int> next_node(ni,nj);
if (visited.find(next_node) != visited.end()) continue;
if (blocked_set.find(next_node) != blocked_set.end()) continue;
q.push(next_node);
visited.insert(next_node);
}
}
return visited.size() > MAX;
};
return check(source,target) && check(target,source);
}
};
python3代码如下,
class Solution:
def isEscapePossible(self, blocked: List[List[int]], source: List[int], target: List[int]) -> bool:
#从起点进行bfs,它能访问到结点的数量大于MAX。并且,从终点进行bfs,它能访问到结点的数量大于MAX。此时,返回True。否则,返回False。
#n = blocked.size(), 有n个障碍物,利用边界,它所能围成的最大面积为(n-1)*n//2。故MAX=(n-1)*n//2。
#还有一种情况,即便能够访问到的结点的数量小于等于MAX,也返回True。即,起点能访问到终点。
n = int(1e6)
t = len(blocked)
blocked = [tuple(x) for x in blocked]
blocked = set(blocked)
MAX = t*(t-1)//2
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
def check(snode: list, enode: list) -> bool:
snode = tuple(snode)
enode = tuple(enode)
q = collections.deque([])
visited = set()
q.append(snode)
visited.add(snode)
while len(q) > 0 and len(visited) <= MAX:
i, j = q.popleft()
if (i,j) == enode:
return True
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= n:
continue
if (ni,nj) in visited:
continue
if (ni,nj) in blocked:
continue
q.append((ni,nj))
visited.add((ni,nj))
return len(visited) > MAX
return check(source, target) and check(target, source)
题目42:864. 获取所有钥匙的最短路径
解题思路:状态压缩+bfs。前两维表示位置,后一维表示当前拥有的钥匙组合。
C++代码如下,
class Solution {
public:
int shortestPathAllKeys(vector<string>& grid) {
int n = grid.size();
int m = grid[0].size();
int t = 0; //钥匙的数目
int si = 0, sj = 0; //起点
string str1 = "abcdef"; //钥匙串
string str2 = "ABCDEF"; //锁串
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (grid[i][j] == '@') {
si = i;
sj = j;
} else if (str1.find(grid[i][j]) != string::npos) {
t += 1;
}
}
}
//总共有t把钥匙
int p = 1 << t; //第三维的最大值
vector<vector<vector<int>>> d(n, vector<vector<int>>(m, vector<int>(p, -1)));
queue<vector<int>> q;
vector<int> vec1 = {si,sj,0};
q.push(vec1);
d[si][sj][0] = 0;
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
while (!q.empty()) {
auto vec1 = q.front();
q.pop();
int i = vec1[0];
int j = vec1[1];
int s = vec1[2];
if (s == p-1) { //返回答案
return d[i][j][s];
}
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) { //超过边界了,跳过
continue;
}
if (grid[ni][nj] == '#') { //是一堵墙,跳过
continue;
}
char c = grid[ni][nj];
int ns = s;
if (str1.find(c) != string::npos) { //是一把钥匙
int idx = c - 'a';
if ((ns >> idx) & 1) { //之前拾取过这把钥匙
//pass
} else {
ns += 1 << idx;
}
}
if (d[ni][nj][ns] != -1) { //这个状态计算过了,跳过
continue;
}
if (str2.find(c) != string::npos) { //是一把锁
int idx = c - 'A';
if ((ns >> idx) & 1) { //有对应的钥匙
//pass
} else { //没有对应的钥匙,跳过
continue;
}
}
vector<int> vec2 = {ni,nj,ns};
q.push(vec2);
d[ni][nj][ns] = d[i][j][s] + 1;
}
}
return -1;
}
};
python3代码如下,
class Solution:
def shortestPathAllKeys(self, grid: List[str]) -> int:
n, m = len(grid), len(grid[0])
#grid[i][j] 只含有 '.', '#', '@', 'a'-'f' 以及 'A'-'F'
#返回获取所有钥匙所需要的移动的最少次数
#如果无法获取所有钥匙,返回 -1
#使用状态压缩求解
t = 0 #钥匙的数目
si, sj = -1, -1
for i in range(n):
for j in range(m):
if grid[i][j] == '@':
si, sj = i, j
elif grid[i][j] in "abcdef":
t += 1
p = 1 << t #t最大是6
d = [[[-1] * p for _ in range(m)] for _ in range(n)]
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
q = collections.deque([])
q.append([si,sj,0])
d[si][sj][0] = 0
while len(q) > 0:
tot = len(q)
while tot > 0:
tot -= 1
i,j,s = q.popleft()
if s == p-1: #提前返回
return d[i][j][s]
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m: #超过边界
continue
if grid[ni][nj] == '#': #是一堵墙
continue
ns = s
if grid[ni][nj] in "abcdef": #是一把钥匙
idx = ord(grid[ni][nj]) - ord('a')
if (ns >> idx) & 1:
pass #这把钥匙已经获取过了
else:
ns += (1 << idx)
if d[ni][nj][ns] != -1: #这个状态计算过了
continue
if grid[ni][nj] in "ABCDEF": #是一把锁
idx = ord(grid[ni][nj]) - ord('A')
if (ns >> idx) & 1:
pass #有钥匙
else: #没有钥匙
continue
d[ni][nj][ns] = d[i][j][s] + 1
q.append([ni,nj,ns])
return -1
题目43:1263. 推箱子
解题思路:记录当前的箱子的位置(bi,bj)
、当前人的位置(pi,pj)
,遍历四个方向,考虑下一步箱子的位置(nbi,nbj)
,而下一步人的位置(npi,npj)
可以由下一步箱子(nbi,nbj)
的位置推导出来。然后需要判断,人能不能从(pi,pj)
走到(npi,npj)
,此时箱子在(bi,bj)
。
C++代码如下,
class Solution {
public:
int minPushBox(vector<vector<char>>& grid) {
int n = grid.size();
int m = grid[0].size();
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
int bi = -1, bj = -1; //箱子的初始位置
int pi = -1, pj = -1; //人的初始位置
int ei = -1, ej = -1; //目标位置
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (grid[i][j] == 'B') {
bi = i;
bj = j;
} else if (grid[i][j] == 'S') {
pi = i;
pj = j;
} else if (grid[i][j] == 'T') {
ei = i;
ej = j;
}
}
}
function<bool(int,int,int,int,int,int)> check =[&] (int bi, int bj, int pi, int pj, int npi, int npj) -> bool {
vector<vector<bool>> st(n, vector<bool>(m, false));
queue<pair<int,int>> q;
q.push(make_pair(pi,pj));
st[pi][pj] = true;
while (!q.empty()) {
auto [i,j] = q.front();
if (i == npi && j == npj) { //走到了终点,返回true
return true;
}
q.pop();
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (grid[ni][nj] == '#' || (ni == bi && nj == bj)) continue;
if (st[ni][nj]) continue;
q.push(make_pair(ni,nj));
st[ni][nj] = true;
}
}
return false;
};
vector<vector<vector<vector<int>>>> d(n, vector<vector<vector<int>>>(m, vector<vector<int>>(n, vector<int>(m, -1))));
queue<vector<int>> q;
q.push({bi,bj,pi,pj});
d[bi][bj][pi][pj] = 0;
while (!q.empty()) {
auto t = q.front();
q.pop();
int bi = t[0];
int bj = t[1]; //箱子的当前位置(bi,bj)
int pi = t[2];
int pj = t[3]; //人的当前位置(pi,pj)
if (bi == ei && bj == ej) {
return d[bi][bj][pi][pj];
}
for (int k = 0; k < 4; ++k) {
int nbi = bi + dirs[k][0];
int nbj = bj + dirs[k][1]; //箱子的下一步位置(nbi,nbj)
int k1 = (k+2)%4;
int npi = bi + dirs[k1][0];
int npj = bj + dirs[k1][1]; //人的下一步位置(npi,npj)
if (nbi < 0 || nbi >= n || nbj < 0 || nbj >= m) continue;
if (npi < 0 || npi >= n || npj < 0 || npj >= m) continue;
if (grid[nbi][nbj] == '#') continue;
if (grid[npi][npj] == '#') continue;
if (d[nbi][nbj][npi][npj] != -1) continue;
//箱子位置在(bi,bj),人能从(pi,pj)走到(npi,npj)
if (!check(bi,bj,pi,pj,npi,npj)) continue;
q.push({nbi,nbj,npi,npj});
d[nbi][nbj][npi][npj] = d[bi][bj][pi][pj] + 1;
}
}
return -1;
}
};
python3代码如下,
#我的题解
class Solution:
def minPushBox(self, grid: List[List[str]]) -> int:
#返回将箱子推到目标位置的最小推动次数
n, m = len(grid), len(grid[0])
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
def check(bi,bj,pi,pj,npi,npj): #箱子的位置在(bi,bj),当前人的位置在(pi,pj),人下一步的位置在(npi,npj),能走到目标处
st = [[False] * m for _ in range(n)]
q = collections.deque([])
q.append((pi,pj))
st[pi][pj] = True
while len(q) > 0:
i,j = q.popleft()
if i == npi and j == npj:
return True
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if grid[ni][nj] == '#' or (ni == bi and nj == bj):
continue
if st[ni][nj]:
continue
q.append((ni,nj))
st[ni][nj] = True
return False
snode = None #人的位置
bnode = None #箱子的位置
enode = None #目标的位置
for i in range(n):
for j in range(m):
if grid[i][j] == 'S':
snode = (i,j)
elif grid[i][j] == 'B':
bnode = (i,j)
elif grid[i][j] == 'T':
enode = (i,j)
#计算答案
d = [[[[-1] * m for _ in range(n)] for _ in range(m)] for _ in range(n)]
q = collections.deque([])
q.append((bnode[0],bnode[1],snode[0],snode[1]))
d[bnode[0]][bnode[1]][snode[0]][snode[1]] = 0
while len(q) > 0:
bi,bj,pi,pj = q.popleft() #箱子的位置为(bi,bj),当前人的位置为(pi,pj)
if (bi,bj) == enode: #返回答案
return d[bi][bj][pi][pj]
for k in range(4):
nbi = bi + dirs[k][0] #箱子下一步的位置(nbi,nbj)
nbj = bj + dirs[k][1]
k1 = (k+2)%4
npi = bi + dirs[k1][0] #人下一步的位置(npi,npj)
npj = bj + dirs[k1][1]
if nbi < 0 or nbi >= n or nbj < 0 or nbj >= m:
continue
if npi < 0 or npi >= n or npj < 0 or npj >= m:
continue
if grid[nbi][nbj] == '#':
continue
if grid[npi][npj] == '#':
continue
if d[nbi][nbj][npi][npj] != -1:
continue
if not check(bi,bj,pi,pj,npi,npj): #箱子的位置在(bi,bj),当前人的位置在(pi,pj),人下一步的位置在(npi,npj),能走到目标处
continue
q.append((nbi,nbj,npi,npj))
d[nbi][nbj][npi][npj] = d[bi][bj][pi][pj] + 1
return -1
#官方题解
class Solution:
def minPushBox(self, grid: List[List[str]]) -> int:
m = len(grid)
n = len(grid[0])
sx, sy, bx, by = None, None, None, None
for x in range(m):
for y in range(n):
if grid[x][y] == 'S':
sx = x
sy = y
elif grid[x][y] == 'B':
bx = x
by = y
#不越界且不在墙上
def ok(x, y):
return (0 <= x < m and 0 <= y < n and grid[x][y] != '#')
d = [0,-1,0,1,0]
dp = [[float('inf')] * (m*n) for _ in range(m*n)]
dp[sx*n+sy][bx*n+by] = 0 #初始状态的推动次数为0
q = deque([(sx*n+sy,bx*n+by)])
while q:
q1 = deque()
while q:
s1, b1 = q.popleft()
sx1, sy1 = s1 // n, s1 % n
bx1, by1 = b1 // n, b1 % n
if grid[bx1][by1] == 'T': #箱子已被推到目标处
return dp[s1][b1]
for i in range(4):
sx2, sy2 = sx1 + d[i], sy1 + d[i+1]
s2 = sx2 * n + sy2
if not ok(sx2, sy2): #玩家位置不合法
continue
if sx2 == bx1 and sy2 == by1: #推动箱子
bx2, by2 = bx1 + d[i], by1 + d[i+1]
b2 = bx2 * n + by2
if not ok(bx2, by2) or dp[s2][b2] <= dp[s1][b1] + 1:
continue
dp[s2][b2] = dp[s1][b1] + 1
q1.append((s2,b2))
else:
if dp[s2][b1] <= dp[s1][b1]: #状态已访问
continue
dp[s2][b1] = dp[s1][b1]
q.append((s2,b1))
q, q1 = q1, q
return -1
题目44:2258. 逃离火灾
解题思路:二分+两遍bfs。
C++代码如下,
class Solution {
public:
int maximumMinutes(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
vector<vector<int>> d(n, vector<int>(m, -1)); //d[i][j]表示火走到(i,j)处所花费的时间
queue<pair<int,int>> q;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (grid[i][j] == 1) {
q.push(make_pair(i,j));
d[i][j] = 0;
}
}
}
while (!q.empty()) {
auto [i,j] = q.front();
q.pop();
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (grid[ni][nj] == 2) continue;
if (d[ni][nj] != -1) continue;
q.push(make_pair(ni,nj));
d[ni][nj] = d[i][j] + 1;
}
}
function<bool(int)> check =[&] (int mid) -> bool {
queue<pair<int,int>> q;
vector<vector<bool>> st(n, vector<bool>(m, false));
q.push(make_pair(0,0));
st[0][0] = true;
int level = mid;
while (!q.empty()) {
int tot = q.size();
while (tot--) {
auto [i,j] = q.front(); //(i,j)的时间为level
q.pop();
if (i == n-1 && j == m-1) { //走到了终点,返回true
return true;
}
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (grid[ni][nj] == 2) continue;
if (ni == n-1 && nj == m-1) {
if (d[ni][nj] != -1 && level+1 > d[ni][nj]) continue;
} else {
if (d[ni][nj] != -1 && level+1 >= d[ni][nj]) continue;
}
if (st[ni][nj]) continue;
q.push(make_pair(ni,nj));
st[ni][nj] = true;
}
}
level += 1;
}
return false;
};
int left = 0;
int right = 1e9;
int res = -1;
while (left <= right) {
int mid = (left + right) / 2;
if (check(mid)) {
res = mid;
//求最大值
left = mid + 1;
} else {
right = mid - 1;
}
}
return res;
}
};
python3代码如下,
class Solution:
def maximumMinutes(self, grid: List[List[int]]) -> int:
n, m = len(grid), len(grid[0])
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
d = [[-1] * m for _ in range(n)] #d[i][j]表示火达到(i,j)处的时间
q = collections.deque([])
for i in range(n):
for j in range(m):
if grid[i][j] == 1:
q.append((i,j))
d[i][j] = 0
while len(q) > 0:
i, j = q.popleft()
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if grid[ni][nj] == 2:
continue
if d[ni][nj] != -1:
continue
q.append((ni,nj))
d[ni][nj] = d[i][j] + 1
def check(mid: int) -> bool:
st = [[False] * m for _ in range(n)]
q = collections.deque([])
q.append((0,0))
st[0][0] = True
level = mid
while len(q) > 0:
tot = len(q)
while tot > 0:
tot -= 1
i,j = q.popleft() #(i,j)的时间是level
if i == n-1 and j == m-1: #能到达安全屋,返回True
return True
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if grid[ni][nj] == 2:
continue
#注意,如果你到达安全屋后,火马上到了安全屋,这视为你能够安全到达安全屋
if ni == n-1 and nj == m-1:
if d[ni][nj] != -1 and level + 1 > d[ni][nj]: #火已经到达(ni,nj)了
continue
else:
if d[ni][nj] != -1 and level + 1 >= d[ni][nj]: #火已经到达(ni,nj)了
continue
if st[ni][nj]:
continue
q.append((ni,nj))
st[ni][nj] = True
level += 1
return False
left = 0
right = int(1e9)
res = -1
while left <= right:
mid = (left + right) // 2
if check(mid):
#求最大值
res = mid
left = mid + 1
else:
right = mid - 1
return res
解题思路:本质是找到两条毫不相关的路径,能从起点走到终点。
C++代码如下,
class Solution {
public:
bool isPossibleToCutPath(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
int dirs[2][2] = {{1,0},{0,1}};
auto bfs =[&] (int i, int j, vector<vector<bool>>& st, bool& res, vector<pair<int,int>>& path) -> void {
queue<pair<int,int>> q;
q.push(make_pair(0,0));
st[0][0] = true;
map<pair<int,int>,pair<int,int>> map_y_x;
while (!q.empty()) {
auto [i,j] = q.front();
q.pop();
for (int k = 0; k < 2; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (grid[ni][nj] != 1) continue;
if (st[ni][nj]) continue;
q.push(make_pair(ni,nj));
st[ni][nj] = true;
map_y_x[make_pair(ni,nj)] = make_pair(i,j);
}
}
if (st[n-1][m-1]) { //能走到终点
res = true;
path.clear();
pair<int,int> node(n-1,m-1);
while (map_y_x.find(node) != map_y_x.end()) {
path.push_back(node);
node = map_y_x[node];
}
} else { //不能走到终点
res = false;
path.clear();
}
return;
};
bool res1 = false;
vector<pair<int,int>> path1;
vector<vector<bool>> st(n, vector<bool>(m, false));
bfs(0,0,st,res1,path1);
if (res1) { //能走到终点
vector<vector<bool>> st(n, vector<bool>(m, false));
for (auto [i,j] : path1) {
st[i][j] = true;
}
st[n-1][m-1] = false;
// //输出st
// for (int i = 0; i < n; ++i) {
// for (int j = 0; j < m; ++j) {
// cout << st[i][j] << " ";
// }
// cout << endl;
// }
bool res2 = false;
vector<pair<int,int>> path2;
bfs(0,0,st,res2,path2);
// //输出path2
// for (auto [i,j] : path2) {
// cout << "(" << i << "," << j << "),";
// }
// cout << endl;
if (res2) { //能走到终点
return false;
} else { //不能走到终点
return true;
}
} else { //不能走到终点
return true;
}
}
};
python3代码如下,
class Solution:
def isPossibleToCutPath(self, grid: List[List[int]]) -> bool:
#你可以翻转最多一个格子的值(也可以不翻转)。你不能翻转格子(0, 0)和(m - 1, n - 1)
n, m = len(grid), len(grid[0])
dirs = [[1,0],[0,1]]
def bfs(si: int, sj: int, st: list) -> list:
q = collections.deque([])
q.append((si,sj))
st[si][sj] = True
map_y_x = collections.defaultdict(tuple)
while len(q) > 0:
i, j = q.popleft()
for k in range(2):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if grid[ni][nj] != 1:
continue
if st[ni][nj]:
continue
q.append((ni,nj))
st[ni][nj] = True
map_y_x[(ni,nj)] = (i,j)
path = []
if st[n-1][m-1]:
node = (n-1,m-1)
while node in map_y_x:
path.append(node)
node = map_y_x[node]
return [True, path]
else:
return [False, path]
st = [[False] * m for _ in range(n)]
res1, path1 = bfs(0,0,st)
if res1: #如果能走到终点
st = [[False] * m for _ in range(n)]
for i,j in path1:
st[i][j] = True
st[0][0] = False
st[n-1][m-1] = False
res2, path2 = bfs(0,0,st)
if res2: #如果能走到终点
return False
else: #不能走到终点
return True
else: #不能走到终点
return True
解题思路:二分+bfs。二分到达终点的时间。
C++代码如下,
class Solution {
public:
int minimumTime(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
if (grid[0][1] > 1 && grid[1][0] > 1) return -1; //特判不能走到终点的情况
int left = 0;
int right = 1e6;
int res = -1;
function<bool(int)> check =[&] (int mid) -> bool {
//终点的时间为mid,它能从终点走到起点。
if (mid < grid[n-1][m-1]) return false; //特判
vector<vector<bool>> st(n, vector<bool>(m, false));
queue<pair<int,int>> q;
q.push(make_pair(n-1,m-1));
st[n-1][m-1] = true;
int level = mid;
while (!q.empty()) {
int tot = q.size();
while (tot--) {
auto [i,j] = q.front(); //(i,j)的时间为level
q.pop();
if (i == 0 && j == 0) return true; //返回答案
for (int k = 0; k < 4; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (level-1 < grid[ni][nj]) continue;
if (st[ni][nj]) continue;
q.push(make_pair(ni,nj));
st[ni][nj] = true;
}
}
level -= 1;
}
return false;
};
while (left <= right) {
int mid = (left + right) / 2;
if (check(mid)) {
//求最小值
res = mid;
right = mid-1;
} else {
left = mid+1;
}
}
res = res + (res-n-m+2)%2;
return res;
}
};
python3代码如下,
#二分+bfs解法
class Solution:
def minimumTime(self, grid: List[List[int]]) -> int:
#二分到达终点的时间
#到达(i,j)的最短时间的奇偶性与(i+j)的奇偶性一致
n, m = len(grid), len(grid[0])
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
if grid[0][1] > 1 and grid[1][0] > 1: #特判无法到达终点的情况
return -1
left = 0
right = int(1e6)
res = -1
def check(mid: int) -> bool:
if grid[n-1][m-1] > mid: #特判
return False
#到达终点的时间为mid,它能从终点走到起点
st = [[False] * m for _ in range(n)]
q = collections.deque([])
q.append((n-1,m-1))
st[n-1][m-1] = True
level = mid
while len(q) > 0:
tot = len(q)
while tot > 0:
tot -= 1
i,j = q.popleft() #(i,j)的时间为level
if i == 0 and j == 0: #能走到起点,返回答案
return True
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if st[ni][nj]:
continue
if level-1 < grid[ni][nj]:
continue
q.append((ni,nj))
st[ni][nj] = True
level -= 1
return False
while left <= right:
mid = (left + right) // 2
if check(mid):
#求最小值
res = mid
right = mid-1
else:
left = mid+1
res = res + (res-(n-1)-(m-1))%2
return res
#dijkstra解法
class Solution:
def minimumTime(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
if grid[0][1] > 1 and grid[1][0] > 1:
return -1
dis = [[inf] * n for _ in range(m)]
dis[0][0] = 0
h = [(0,0,0)]
while True:
d, i, j = heappop(h)
if d > dis[i][j]:
continue
if i == m-1 and j == n-1:
return d
for x, y in (i+1,j),(i-1,j),(i,j+1),(i,j-1):
if 0 <= x < m and 0 <= y < n:
nd = max(d+1, grid[x][y])
nd += (nd-x-y)%2 #nd必须和x+y同奇偶
if nd < dis[x][y]:
dis[x][y] = nd
heappush(h,(nd,x,y))
题目47:2617. 网格图中最少访问的格子数
解题思路:最小堆+贪心。
C++代码如下,
typedef pair<int,int> PII;
const int inf = 1e6;
class Solution {
public:
int minimumVisitedCells(vector<vector<int>>& grid) {
int n = grid.size();
int m = grid[0].size();
int f = inf;
vector<priority_queue<PII,vector<PII>,greater<PII>>> col_hs(m, priority_queue<PII,vector<PII>,greater<PII>>());
for (int i = 0; i < n; ++i) {
auto row = grid[i];
priority_queue<PII,vector<PII>,greater<PII>> row_h;
for (int j = 0; j < m; ++j) {
int g = row[j];
priority_queue<PII,vector<PII>,greater<PII>>& col_h = col_hs[j];
while (!row_h.empty() && row_h.top().second < j) {
row_h.pop();
}
while (!col_h.empty() && col_h.top().second < i) {
col_h.pop();
}
f = inf;
if (i == 0 && j == 0) f = 1;
if (!row_h.empty()) {
f = row_h.top().first + 1;
}
if (!col_h.empty()) {
int t = col_h.top().first + 1;
f = min(f, t);
}
if (g > 0 && f < inf) {
row_h.push(make_pair(f,g+j));
col_h.push(make_pair(f,g+i));
}
}
}
return f < inf ? f : -1;
}
};
python3代码如下,
#my ans
class Solution:
def minimumVisitedCells(self, grid: List[List[int]]) -> int:
n, m = len(grid), len(grid[0])
col_hs = [[] for _ in range(m)]
for i,row in enumerate(grid):
row_h = [] #第i行的最小堆
for j, (g,col_h) in enumerate(zip(row,col_hs)):
while row_h and row_h[0][1] < j:
heappop(row_h)
while col_h and col_h[0][1] < i:
heappop(col_h)
f = inf if i or j else 1
if row_h:
f = row_h[0][0] + 1
if col_h:
f = min(f, col_h[0][0] + 1)
if g > 0 and f < inf:
heappush(row_h, (f,g+j))
heappush(col_h, (f,g+i))
return f if f < inf else -1
#最小堆+贪心
class Solution:
def minimumVisitedCells(self, grid: List[List[int]]) -> int:
col_heaps = [[] for _ in grid[0]]
for i, row in enumerate(grid):
row_h = []
for j, (g, col_h) in enumerate(zip(row,col_heaps)):
while row_h and row_h[0][1] < j:
heappop(row_h)
while col_h and col_h[0][1] < i:
heappop(col_h)
f = inf if i or j else 1
if row_h:
f = row_h[0][0] + 1
if col_h:
f = min(f, col_h[0][0] + 1)
if g and f < inf:
heappush(row_h, (f, g+j))
heappush(col_h, (f, g+i))
return f if f < inf else -1
题目48:LCP 13. 寻宝
解题思路:
(1)计算起点-石块-第i
个机关的最小距离d0[i]
。
(2)计算第i
个机关-石块-第j
个机关的最小距离d1[i][j]
。
(3)计算第i
个机关-终点的最小距离d2[i]
。
(4)动态规划,当前已经访问了第i
个机关,已访问的机关的状态为mask
,此时的最小距离为dp[i][mask]
。
(5)注意,动态规划转移顺序,
for (int mask = 0; mask < (1 << nb); ++mask) {
for (int i = 0; i < nb; ++i) {
for (int j = 0; j < nb; ++j) {
//process
}
}
}
C++代码如下,
//mine
class Solution {
public:
void get_d(vector<vector<int>>& d, const int sx, const int sy, const vector<string>& grid) {
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
int n = grid.size();
int m = grid[0].size();
queue<pair<int,int>> q;
q.push({sx,sy});
d[sx][sy] = 0;
while (!q.empty()) {
auto [x,y] = q.front();
q.pop();
for (int k = 0; k < 4; ++k) {
int nx = x + dirs[k][0];
int ny = y + dirs[k][1];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if (d[nx][ny] != -1) continue;
if (grid[nx][ny] == '#') continue;
q.push({nx,ny});
d[nx][ny] = d[x][y] + 1;
}
}
return;
}
void get_d_stone(vector<vector<vector<int>>>& d_stone, const vector<string>& grid, const vector<pair<int,int>>& stones) {
int n = grid.size();
int m = grid[0].size();
int ns = stones.size();
for (int i = 0; i < ns; ++i) {
auto [sx,sy] = stones[i];
vector<vector<int>> d(n, vector<int>(m, -1)); //用-1初始化
get_d(d, sx, sy, grid);
d_stone[i] = d;
}
return;
}
void get_d0(vector<int>& d0, const vector<vector<vector<int>>>& d_stone, const vector<vector<int>>& d_start, const vector<pair<int,int>>& stones, const vector<pair<int,int>>& buttons) {
int ns = stones.size();
int nb = buttons.size();
for (int i = 0; i < nb; ++i) {
//计算起点-石块-第i个机关的最小距离d0[i]
auto [x1, y1] = buttons[i]; //第i个机关的坐标(x1,y1)
int ans = -1;
for (int j = 0; j < ns; ++j) {
auto [x2, y2] = stones[j]; //第j个石头的坐标(x2,y2)
if (d_start[x2][y2] == -1 || d_stone[j][x1][y1] == -1) continue;
int curr = d_start[x2][y2] + d_stone[j][x1][y1];
if (ans == -1) {
ans = curr;
}
else {
ans = min(ans, curr);
}
}
d0[i] = ans;
}
return;
}
void get_d1(vector<vector<int>>& d1, const vector<vector<vector<int>>>& d_stone, const vector<pair<int,int>>& buttons, const vector<pair<int,int>>& stones) {
int ns = stones.size();
int nb = buttons.size();
for (int i = 0; i < nb; ++i) {
auto [x1, y1] = buttons[i]; //第i个机关的坐标(x1,y1)
for (int j = i+1; j < nb; ++j) {
//计算d1[i][j]
auto [x2, y2] = buttons[j]; //第j个机关的坐标(x2,y2)
int ans = -1;
for (int k = 0; k < ns; ++k) {
if (d_stone[k][x1][y1] == -1 || d_stone[k][x2][y2] == -1) continue;
int curr = d_stone[k][x1][y1] + d_stone[k][x2][y2];
if (ans == -1) {
ans = curr;
} else {
ans = min(ans, curr);
}
}
d1[i][j] = d1[j][i] = ans;
}
}
return;
}
void get_d2(vector<int>& d2, const vector<pair<int,int>>& buttons, const int ex, const int ey, const vector<string>& grid) {
int n = grid.size();
int m = grid[0].size();
int nb = buttons.size();
int dirs[4][2] = {{-1,0},{0,-1},{1,0},{0,1}};
queue<pair<int,int>> q;
q.push({ex,ey});
vector<vector<int>> d(n, vector<int>(m, -1));
d[ex][ey] = 0;
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
for (int k = 0; k < 4; ++k) {
int nx = x + dirs[k][0];
int ny = y + dirs[k][1];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if (grid[nx][ny] == '#') continue;
if (d[nx][ny] != -1) continue;
d[nx][ny] = d[x][y] + 1;
q.push({nx,ny});
}
}
for (int i = 0; i < nb; ++i) {
auto [x, y] = buttons[i];
d2[i] = d[x][y];
}
return;
}
int minimalSteps(vector<string>& grid) {
int n = grid.size();
int m = grid[0].size();
vector<pair<int,int>> stones; //石头的坐标
vector<pair<int,int>> buttons; //机关的坐标
int sx = -1, sy = -1; //起点的坐标
int ex = -1, ey = -1; //终点的坐标
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (grid[i][j] == 'O') {
stones.push_back({i,j});
} else if (grid[i][j] == 'M') {
buttons.push_back({i,j});
} else if (grid[i][j] == 'S') {
sx = i;
sy = j;
} else if (grid[i][j] == 'T') {
ex = i;
ey = j;
}
}
}
//检查起点和终点对不对
//cout << "sx = " << sx << ", sy = " << sy << ", ex = " << ex << ", ey = " << ey << endl;
//检查石头的坐标
// for (int i = 0; i < stones.size(); ++i) {
// auto [x,y] = stones[i];
// cout << "i = " << i << ", x = " << x << ", y = " << y << endl;
// }
//检查机关的坐标
// for (int i = 0; i < buttons.size(); ++i) {
// auto [x,y] = buttons[i];
// cout << "i = " << i << ", x = " << x << ", y = " << y << endl;
// }
int ns = stones.size(); //石块的数目ns
int nb = buttons.size(); //机关的数目nb
//计算第i个石块到坐标(x,y)的最小距离d_stone[i][x][y]
vector<vector<vector<int>>> d_stone(ns, vector<vector<int>>(n, vector<int>(m, -1)));
//不能到达的距离初始化为-1
get_d_stone(d_stone, grid, stones);
//存在机关到达不了石块,则返回-1
for (int i = 0; i < nb; ++i) {
auto [x1, y1] = buttons[i]; //第i个机关的坐标(x1,y1)
bool flag = false;
for (int j = 0; j < ns; ++j) {
if (d_stone[j][x1][y1] != -1) { //第j个石块能够抵达坐标(x1,y1)
flag = true;
break;
}
}
if (!flag) {
return -1;
}
}
//检查d_stone
// for (int i = 0; i < ns; ++i) {
// cout << "====start i = " << i << endl;
// for (int x = 0; x < n; ++x) {
// for (int y = 0; y < m; ++y) {
// cout << d_stone[i][x][y] << ",";
// }
// cout << endl;
// }
// }
//计算起点到坐标(x,y)的最小距离d_start[x][y]
vector<vector<int>> d_start(n, vector<int>(m, -1));
get_d(d_start, sx, sy, grid);
//存在机关到达不了起点,则返回-1
for (int i = 0; i < nb; ++i) {
auto [x1, y1] = buttons[i]; //第i个机关的坐标(x1,y1)
if (d_start[x1][y1] == -1) {
return -1;
}
}
//机关数目为0,直接返回起点到终点的距离
if (nb == 0) {
return d_start[ex][ey];
}
//检查d_start
// for (int x = 0; x < n; ++x) {
// for (int y = 0; y < m; ++y) {
// cout << d_start[x][y] << ",";
// }
// cout << endl;
// }
//计算起点-石块-第i个机关的最小距离d0[i]
vector<int> d0(nb, -1); //-1表示不能到达
get_d0(d0, d_stone, d_start, stones, buttons);
//检查d0
// cout << "d0:";
// for (int i = 0; i < nb; ++i) {
// cout << d0[i] << ",";
// }
// cout << endl;
//计算第i个机关-石块-第j个机关的最小距离d1[i][j]
vector<vector<int>> d1(nb, vector<int>(nb, -1));
get_d1(d1, d_stone, buttons, stones);
//检查d1
// for (int i = 0; i < nb; ++i) {
// for (int j = 0; j < nb; ++j) {
// cout << d1[i][j] << ",";
// }
// cout << endl;
// }
//计算终点-第i个机关的最小距离d2[i]
vector<int> d2(nb, -1);
get_d2(d2, buttons, ex, ey, grid);
//存在机关达到不了终点,返回-1
for (int i = 0; i < nb; ++i) {
if (d2[i] == -1) {
return -1;
}
}
//检查d2
// for (int i = 0; i < nb; ++i) {
// cout << d2[i] << ",";
// }
// cout << endl;
//动态规划
const int inf = 0x3f3f3f3f;
vector<vector<int>> dp(nb, vector<int>(1<<nb, -1));
//初始化
for (int i = 0; i < nb; ++i) {
dp[i][1<<i] = d0[i];
}
//检查初始化之后的dp
// for (int i = 0; i < nb; ++i) {
// for (int mask = 0; mask < (1<<nb); ++mask) {
// cout << dp[i][mask] << ",";
// }
// cout << endl;
// }
//状态转移
// for (int i = 0; i < nb; ++i) {
// for (int mask = 0; mask < (1<<nb); ++mask) {
// if ((mask>>i)&1) {
// for (int k = 0; k < nb; ++k) {
// if ((mask>>k)&1) {
// if (dp[i][mask] == -1 || dp[i][mask] > dp[k][mask-(1<<i)] + d1[k][i]) {
// if (dp[k][mask-(1<<i)] == -1 || d1[k][i] == -1) continue;
// //cout << "i = " << i << ", mask = " << mask << ", k = " << k << endl;
// //cout << dp[i][mask] << "," << dp[k][mask-(1<<i)] << "," << d1[k][i] << endl;
// dp[i][mask] = dp[k][mask-(1<<i)] + d1[k][i];
// }
// }
// }
// }
// }
// }
//状态转移
for (int mask = 1; mask < (1 << nb); ++mask) {
for (int i = 0; i < nb; ++i) {
if (mask & (1<<i)) {
for (int j = 0; j < nb; ++j) {
if (!(mask & (1 << j))) {
int next = mask | (1 << j);
if (dp[j][next] == -1 || dp[j][next] > dp[i][mask] + d1[i][j]) {
dp[j][next] = dp[i][mask] + d1[i][j];
}
}
}
}
}
}
//计算最终答案
int res = -1;
for (int i = 0; i < nb; ++i) {
//auto [x1, y1] = buttons[i]; //第i个机关的坐标(x1,y1)
if (dp[i][(1<<nb)-1] == -1) continue;
int ans = dp[i][(1<<nb)-1] + d2[i];
if (res == -1) {
res = ans;
} else {
res = min(res, ans);
}
}
return res;
}
};
//官方解答
class Solution {
public:
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
int n, m;
bool inBound(int x, int y) {
return x >= 0 && x < n && y >= 0 && y < m;
}
vector<vector<int>> bfs(int x, int y, vector<string>& maze) {
vector<vector<int>> ret(n, vector<int>(m, -1));
ret[x][y] = 0;
queue<pair<int,int>> Q;
Q.push({x,y});
while (!Q.empty()) {
auto p = Q.front();
Q.pop();
int x = p.first, y = p.second;
for (int k = 0; k < 4; ++k) {
int nx = x + dx[k], ny = y + dy[k];
if (inBound(nx,ny) && maze[nx][ny] != '#' && ret[nx][ny] == -1) {
ret[nx][ny] = ret[x][y] + 1;
Q.push({nx,ny});
}
}
}
return ret;
}
int minimalSteps(vector<string>& maze) {
n = maze.size(), m = maze[0].size();
//机关和石头
vector<pair<int,int>> buttons, stones;
//起点和终点
int sx, sy, tx, ty;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (maze[i][j] == 'M') {
buttons.push_back({i,j});
}
if (maze[i][j] == 'O') {
stones.push_back({i,j});
}
if (maze[i][j] == 'S') {
sx = i, sy = j;
}
if (maze[i][j] == 'T') {
tx = i, ty = j;
}
}
}
int nb = buttons.size();
int ns = stones.size();
vector<vector<int>> start_dist = bfs(sx, sy, maze);
//特判没有机关的情况,直接返回start_dist[tx][ty]
if (nb == 0) {
return start_dist[tx][ty];
}
//从某个机关到其他机关、起点和终点的最短距离
vector<vector<int>> dist(nb, vector<int>(nb+2, -1));
//dist[i][nb+1]:第i个机关-终点的最短距离。
//dist[i][nb]:起点-石块-第i个机关的最短距离。
//dist[i][j]:第i个机关-石块-第j个机关的最小距离。
//中间结果
vector<vector<vector<int>>> dd(nb);
//dd[i]:以第i个机关为起点,到每个网格的最短距离。
for (int i = 0; i < nb; ++i) {
vector<vector<int>> d = bfs(buttons[i].first, buttons[i].second, maze);
dd[i] = d;
//从某个点到终点不需要拿石头
dist[i][nb+1] = d[tx][ty];
}
for (int i = 0; i < nb; ++i) {
int tmp = -1;
for (int k = 0; k < ns; ++k) {
int mid_x = stones[k].first, mid_y = stones[k].second;
if (dd[i][mid_x][mid_y] != -1 && start_dist[mid_x][mid_y] != -1) {
if (tmp == -1 || tmp > dd[i][mid_x][mid_y] + start_dist[mid_x][mid_y]) {
tmp = dd[i][mid_x][mid_y] + start_dist[mid_x][mid_y];
}
}
}
dist[i][nb] = tmp;
for (int j = i + 1; j < nb; ++j) {
int mn = -1;
for (int k = 0; k < ns; ++k) {
int mid_x = stones[k].first, mid_y = stones[k].second;
if (dd[i][mid_x][mid_y] != -1 && dd[j][mid_x][mid_y] != -1) {
if (mn == -1 || mn > dd[i][mid_x][mid_y] + dd[j][mid_x][mid_y]) {
mn = dd[i][mid_x][mid_y] + dd[j][mid_x][mid_y];
}
}
}
dist[i][j] = mn;
dist[j][i] = mn;
}
}
//特判存在机关无法到达起点或者终点的情况,此时返回-1。
for (int i = 0; i < nb; ++i) {
if (dist[i][nb] == -1 || dist[i][nb+1] == -1) return -1;
}
//dp数组,-1代表没有遍历到
//dp[mask][i]:当前已触发第i个机关,机关的触发状态为mask,此时的最小距离
vector<vector<int>> dp(1 << nb, vector<int>(nb, -1));
for (int i = 0; i < nb; ++i) {
dp[1<<i][i] = dist[i][nb];
}
//由于更新的状态都比未更新的大,所以直接从小到大遍历即可
for (int mask = 1; mask < (1 << nb); mask++) {
for (int i = 0; i < nb; ++i) {
//当前dp是合法的
if (mask & (1 << i)) {
for (int j = 0; j < nb; ++j) {
//j不在mask里·
if (!(mask & (1 << j))) {
int next = mask | (1 << j);
if (dp[next][j] == -1 || dp[next][j] > dp[mask][i] + dist[i][j]) {
dp[next][j] = dp[mask][i] + dist[i][j];
}
}
}
}
}
}
int ret = -1;
int final_mask = (1 << nb) - 1;
for (int i = 0; i < nb; ++i) {
if (ret == -1 || ret > dp[final_mask][i] + dist[i][nb+1]) {
ret = dp[final_mask][i] + dist[i][nb+1];
}
}
return ret;
}
};
python3代码如下,
class Solution:
def minimalSteps(self, grid: List[str]) -> int:
n = len(grid)
m = len(grid[0])
snode = [-1,-1]
enode = [-1,-1]
stones = []
buttons = []
for i in range(n):
for j in range(m):
if grid[i][j] == 'S':
snode = [i,j]
elif grid[i][j] == 'T':
enode = [i,j]
elif grid[i][j] == 'M':
buttons.append([i,j])
elif grid[i][j] == 'O':
stones.append([i,j])
dirs = [[-1,0],[0,-1],[1,0],[0,1]]
#检查snode,enode,stones,buttons
#print(f"snode={snode},enode={enode},stones={stones},buttons={buttons}.")
def get_dist(sx, sy) -> list:
dist = [[-1] * m for _ in range(n)]
q = collections.deque([])
q.append([sx,sy])
dist[sx][sy] = 0
while len(q) > 0:
i,j = q.popleft()
for k in range(4):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if dist[ni][nj] != -1:
continue
if grid[ni][nj] == '#':
continue
dist[ni][nj] = dist[i][j] + 1
q.append([ni,nj])
return dist
ns = len(stones)
nb = len(buttons)
dist_start = get_dist(snode[0], snode[1])
if nb == 0: #特判机关数为0,直接返回起点到终点的距离
return dist_start[enode[0]][enode[1]]
#存在起点到达不了机关buttons[i],返回-1
for i in range(nb):
x1,y1 = buttons[i] #第i个机关的坐标(x1,y1)
if dist_start[x1][y1] == -1:
return -1
dist_stone = []
for x,y in stones:
dist = get_dist(x,y)
dist_stone.append(dist)
#存在机关buttons[i]到达不了石块,返回-1
for i in range(nb):
x1,y1 = buttons[i] #第i个机关的坐标(x1,y1)
flag = True
for j in range(ns):
x2,y2 = stones[j] #第j个石块的坐标(x2,y2)
if dist_stone[j][x1][y1] != -1: #第j个石块能够到达第i个机关
flag = False
break
if flag:
return -1
#计算起点-石块-第i个机关的最小距离d0[i]
d0 = [-1] * nb
for i in range(nb):
x1, y1 = buttons[i] #第i个机关的坐标(x1,y1)
ans = -1
for j in range(ns):
x2,y2 = stones[j] #第j个石块的坐标(x2,y2)
#dist_start[x2][y2] != -1
#dist_stone[j][x1][y1] != -1
if dist_start[x2][y2] == -1 or dist_stone[j][x1][y1] == -1:
continue
if ans == -1 or ans > dist_start[x2][y2] + dist_stone[j][x1][y1]:
ans = dist_start[x2][y2] + dist_stone[j][x1][y1]
d0[i] = ans
#计算第i个机关-石块-第j个机关的最小距离d1[i][j]
d1 = [[-1] * nb for _ in range(nb)]
for i in range(nb):
x1,y1 = buttons[i] #第i个机关的坐标(x1,y1)
for j in range(i+1,nb):
x2,y2 = buttons[j] #第j个机关的坐标(x2,y2)
ans = -1
for k in range(ns):
x3,y3 = stones[k] #第k个石块的坐标(x3,y3)
#dist_stone[k][x1][y1] != -1
#dist_stone[k][x2][y2] != -1
if dist_stone[k][x1][y1] == -1 or dist_stone[k][x2][y2] == -1:
continue
if ans == -1 or ans > dist_stone[k][x1][y1] + dist_stone[k][x2][y2]:
ans = dist_stone[k][x1][y1] + dist_stone[k][x2][y2]
d1[i][j] = d1[j][i] = ans
#计算第i个机关-终点的最小距离d2[i]
dist_end = get_dist(enode[0],enode[1])
d2 = [-1] * nb
for i in range(nb):
x,y = buttons[i]
d2[i] = dist_end[x][y]
#存在第i个机关到达不了终点
for i in range(nb):
if d2[i] == -1:
return -1
#动态规划
N = 1 << nb
dp = [[-1] * N for _ in range(nb)]
#初始化
for i in range(nb):
mask = 1 << i
dp[i][mask] = d0[i]
#状态转移
for mask in range(0,N):
for i in range(nb):
if (mask >> i) & 1:
for j in range(nb):
if not (mask >> j) & 1:
next_mask = mask | (1 << j)
if d1[i][j] == -1:
continue
if dp[j][next_mask] == -1 or dp[j][next_mask] > dp[i][mask] + d1[i][j]:
dp[j][next_mask] = dp[i][mask] + d1[i][j]
#计算最终答案
res = -1
for i in range(nb):
if dp[i][N-1] == -1 or d2[i] == -1:
continue
if res == -1 or res > dp[i][N-1] + d2[i]:
res = dp[i][N-1] + d2[i]
return res
题目49:LCP 31. 变换的迷宫
解题思路:记忆化搜索。
C++代码如下,
class Solution {
public:
bool escapeMaze(vector<vector<string>>& grid) {
int T = grid.size();
int n = grid[0].size();
int m = grid[0][0].size();
int dirs[5][2] = {{-1,0},{0,-1},{1,0},{0,1},{0,0}}; //可以原地等待
int memo[55][55][105][5][5] = {false};
function<bool(int,int,int,bool,bool)> dfs =[&] (int i, int j, int t, bool f1, bool f2) -> bool {
if (memo[i][j][t][(int)f1][(int)f2]) return false; //已经访问过了,返回false
memo[i][j][t][(int)f1][(int)f2] = true;
bool res = false;
if (i == n-1 && j == m-1) {
return true;
}
if (t+1 >= T) {
return false;
}
if (n-1-i + m-1-j > T-1-t) {
return false;
}
for (int k = 0; k < 5; ++k) {
int ni = i + dirs[k][0];
int nj = j + dirs[k][1];
if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
if (grid[t+1][ni][nj] == '.') {
res = res || dfs(ni,nj,t+1,f1,f2);
} else {
if (f1) {
res = res || dfs(ni,nj,t+1,false,f2);
}
if (f2) {
for (int nt = t+1; nt < T; ++nt) {
res = res || dfs(ni,nj,nt,f1,false);
}
}
}
}
return res;
};
return dfs(0,0,0,true,true);
}
};
python3代码如下,
class Solution:
def escapeMaze(self, grid: List[List[str]]) -> bool:
T = len(grid)
n = len(grid[0])
m = len(grid[0][0])
dirs = [[-1,0],[0,-1],[1,0],[0,1],[0,0]] #可以原地不动
@cache
def dfs(i, j, t, f1, f2) -> bool:
#t时刻,我有卷轴f1和f2,从(i,j)出发,能到达终点
res = False
if i == n-1 and j == m-1:
return True
if t+1 >= T:
return False
if n-1-i + m-1-j > T-1-t:
return False #剪枝
for k in range(5):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if grid[t+1][ni][nj] == '.':
res = res or dfs(ni,nj,t+1,f1,f2)
else:
if f1: #卷轴1
res = res or dfs(ni,nj,t+1,False,f2)
if f2:
for nt in range(t+1,T):
res = res or dfs(ni,nj,nt,f1,False)
return res
return dfs(0,0,0,True,True)
#超时了
class Solution:
def escapeMaze(self, grids: List[List[str]]) -> bool:
step = len(grids)
n = len(grids[0])
m = len(grids[0][0])
if n+m-1 > step: #if n+m-2 > step-1:
return False #最小步数不满足要求
dirs = [[-1,0],[0,-1],[1,0],[0,1],[0,0]] #可以原地不动
visited = set()
#grid[t][i][j]
#grid[i][j][s]
t = 0
q = [(0,0,0,3,-1,-1)]
visited.add((0,0,0,3,-1,-1))
while t+1 < step:
nq = []
for tt,i,j,s,ti,tj in q:
#当前时刻为t
for k in range(5):
ni = i + dirs[k][0]
nj = j + dirs[k][1]
if ni < 0 or ni >= n or nj < 0 or nj >= m:
continue
if grids[t+1][ni][nj] != '#' or (ni == ti and nj == tj): #没有必要使用卷轴
node = (tt+1, ni, nj, s, ti, tj)
if node not in visited:
if ni == n-1 and nj == m-1:
return True
nq.append(node)
visited.add(node)
else: #需要使用卷轴
if s & 1: #可以使用卷轴1
node = (tt+1, ni, nj, s-1, ti, tj)
if node not in visited:
if ni == n-1 and nj == m-1:
return True
nq.append(node)
visited.add(node)
if s & 2: #可以使用卷轴2
node = (tt+1, ni, nj, s-2, ni, nj)
if node not in visited:
if ni == n-1 and nj == m-1:
return True
nq.append(node)
visited.add(node)
q = nq #更新q
t += 1
return False