题目
记忆化搜索,状态DP。
给你一个图,图上有些点不能经过,有些点是钥匙,有些点是锁,到达锁之前必须要持有对应的钥匙,问至少走多少步可以拿到所有的钥匙。
详细见LeetCode684,获取所有钥匙的最短路径。
题解
思路
状态DP,对于一个特定的状态,我持有特定的一些钥匙,我只会最多经历任意一个点1次,那么我们可以用宽度优先搜索来求解,状态的话就用二进制表示钥匙持有的状态。
设共有cnt
个钥匙。
- 起点
dp[s_x][s_y][0]
- 终点
dp[i][j][(1 << cnt) - 1]
- 到达锁点,必须要对应的钥匙
- 到达
#
忽略, - 判断是否已到达过
时间复杂度
O(n*m*2^cnt)
空间复杂度
O(n*m*2^cnt)
代码
class Solution {
public:
int shortestPathAllKeys(vector<string>& grid) {
int n = grid.size();
int m = grid[0].size();
int sx, sy;
int cnts = 0;
unordered_map<char, int> mp; // map key to an integer
// search for starting point
// record keys
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(grid[i][j] <= 'z' && grid[i][j] >= 'a') {
mp[grid[i][j]] = cnts;
cnts++;
}
if(grid[i][j] == '@') {
sx = i;
sy = j;
}
}
}
// cout << sx <<" " << sy <<endl;
vector<vector<vector<int>>> dp(n,vector<vector<int>>(m, vector<int>(1 << cnts, -1)));
queue<tuple<int, int, int>> q;
q.emplace(sx, sy, 0);
int dir[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
dp[sx][sy][0] = 0;
while(q.size()) {
auto [x, y, mask] = q.front();
q.pop();
for(int i = 0; i < 4; i++) {
int tmpx = x + dir[i][0];
int tmpy = y + dir[i][1];
int tmpmask = mask;
if(tmpx >= n || tmpx < 0
|| tmpy >= m || tmpy < 0) continue;
char c = grid[tmpx][tmpy];
// at key point, need to update mask
if(c <= 'z' && c >= 'a') {
// cout << c << " " << mp[c] << endl;
tmpmask = tmpmask | (1 << mp[c]);
} else if(c <= 'Z' && c >= 'A') { // at lock point, check the associated key
if(!(tmpmask & (1 << mp[c - ('A' - 'a')]))) continue;
} else if(c == '#') { // at blocked point
continue;
}
if(dp[tmpx][tmpy][tmpmask] != -1) continue ; // already visited before
dp[tmpx][tmpy][tmpmask] = dp[x][y][mask] + 1;
q.emplace(tmpx, tmpy, tmpmask);
// cout << tmpx << " " << tmpy << " " << bitset<sizeof(tmpmask) * 8>(tmpmask) << endl;
// at end point
if(mask == (1 << cnts) - 1) return dp[tmpx][tmpy][tmpmask] - 1;
}
}
return -1;
}
};