图论:三维搜索

前言

重复刷太多相同的题目很容易产生思维惯性。像习惯了套公式,就忘了判断是否满足使用条件或者不会对公式进行变形。导致题目一点点的改变,就让人产生似是而非的错觉,但其实本质还是一样,只是自己没有培养发现本质的能力。没有总结和思考的刷题,反而会成为限制我们发挥的负担。孔子说过:学而不思则罔。

题目: 864. 获取所有钥匙的最短路径

与空间中的三维不同,我们做题中的三维通常是于状态有关。第三维通常是表示这些状态的集合。想本题中的第三位就是表示收集到的钥匙的状态。本题最多6把钥匙,所以共有2^6 + 1 = 65个状态(如没有钥匙、只有第一把钥匙,有第一把和第三把钥匙等等)。只要我们遍历完地图中每个位置的每个状态,我们必能得到最终结果。因为是求最短路径,所以我们套用广度搜索。

关键点:

1,用三维数组记录遍历状态,但收集的钥匙状态不同地图中的相同位置可以重复进入,所以要用三维。

2,表示钥匙的状态,如果直接用数组表示每把钥匙的状态,不仅要记录的维度增加了,而且判断的复杂度也提高了。所以可以用位运算表示钥匙的状态,用一个整数的6位,每一位表示一把钥匙。当6位全为1时,表示收集完成。

3,注意特殊条件。如只有收集了钥匙之后,第三维才会改变。遇到门的处理。

代码:

三维数组

cpp 复制代码
class Solution {
    vector<vector<int>> idxs;  // 表示方向
    vector<vector<vector<int>>> flag;  // 记录访问过的三维坐标

public:
    Solution() : idxs({{1, 0}, {-1, 0}, {0, 1}, {0, -1}}), 
    flag(31, vector<vector<int>>(31, vector<int>(65, 0))) {}  // 初始化

    int shortestPathAllKeys(vector<string>& grid) {
        int n = grid.size(), m = grid[0].size();
        queue<vector<int>> que;
        int cnt = 0;
        for(int i = 0; i < grid.size(); ++ i){ // 记录起点和钥匙的数量
            for(int j = 0; j < grid[0].size(); ++ j){
                if(grid[i][j] == '@'){
                    que.push({i, j, 0});
                    grid[i][j] = '.';
                    flag[i][j][0] = 1;
                }
                else if(grid[i][j] >= 'a' && grid[i][j] <= 'f') cnt ++;
            }
        }

        int step = 0, ultimately = (1 << cnt) - 1; // 表示步数和集齐钥匙的状态
        while(!que.empty()){
            int sz = que.size();
            for(int i = 0; i < sz; i ++){
                auto D = que.front();
                if(D[2] ==  ultimately) return step;  // 结束条件
                que.pop();

                for(int j = 0; j < 4; j ++){
                    int idx1 = D[0] + idxs[j][0], idx2 = D[1] + idxs[j][1];
                    if(idx1 * idx2 < 0 || idx1 >= grid.size() || idx2 >= grid[0].size() || grid[idx1][idx2] == '#') // 边界条件,越界和撞墙
                        continue;
                    // 特殊条件,已经遍历过和没有钥匙开门
                    if(flag[idx1][idx2][D[2]]) continue;
                    char c = grid[idx1][idx2];
                    if(c > '@' && c < 'G' && (D[2] & (1 << c - 'A')) == 0) continue;

                    int state = D[2];  // 记得更新第三维度
                    if(c >= 'a' && c <= 'f') state |= 1 << c - 'a';
                    que.push({idx1, idx2, state});
                    flag[idx1][idx2][state] = 1;
                }
            }
            step ++;
        }
        return -1;
    }
};

进一步压缩状态,用31-16位表示第一维, 15-8表示第二位,7-0表示第三维。把坐标压缩为一个整数,并用字典标记位置

cpp 复制代码
class Solution {
    vector<vector<int>> idxs;  // 表示方向
    unordered_set<int> visited;  // 记录访问过的三维坐标

public:
    Solution() : idxs({{1, 0}, {-1, 0}, {0, 1}, {0, -1}}){}  // 初始化

    int toBinary(int x, int y, int z){
        return (x << 16) + (y << 8) + z;
    }

    vector<int> fromBinary(int value) {
        vector<int> coor(3);
        coor[2] = value & 0xFF; // 提取最后8位
        coor[1] = (value >> 8) & 0xFF; // 右移8位,然后提取最后8位
        coor[0] = (value >> 16) & 0xFF; // 右移16位,然后提取最后8位
        return coor;
    }

    int shortestPathAllKeys(vector<string>& grid) {
        int n = grid.size(), m = grid[0].size();
        queue<int> que;
        int cnt = 0;
        for(int i = 0; i < grid.size(); ++ i){ // 记录起点和钥匙的数量
            for(int j = 0; j < grid[0].size(); ++ j){
                if(grid[i][j] == '@'){
                    int state = toBinary(i, j, 0);
                    que.push(state);
                    grid[i][j] = '.';
                    visited.insert(state);
                }
                else if(grid[i][j] >= 'a' && grid[i][j] <= 'f') cnt ++;
            }
        }

        int step = 0, ultimately = (1 << cnt) - 1; // 表示步数和集齐钥匙的状态
        while(!que.empty()){
            int sz = que.size();
            for(int i = 0; i < sz; i ++){
                auto x = que.front();
                auto D = fromBinary(x);
                if(D[2] ==  ultimately) return step;  // 结束条件
                que.pop();

                for(int j = 0; j < 4; j ++){
                    int idx1 = D[0] + idxs[j][0], idx2 = D[1] + idxs[j][1];
                    if(idx1 * idx2 < 0 || idx1 >= grid.size() || idx2 >= grid[0].size() || grid[idx1][idx2] == '#') // 边界条件,越界和撞墙
                        continue;
                    // 特殊条件,已经遍历过和没有钥匙开门
                    char c = grid[idx1][idx2];
                    if(c > '@' && c < 'G' && (D[2] & (1 << (c - 'A'))) == 0) continue;

                    int state = D[2];  // 记得更新第三维度
                    if(c >= 'a' && c <= 'f') state |= 1 << (c - 'a');
                    state = toBinary(idx1, idx2, state);
                    if(visited.count(state)) continue;

                    que.push(state);
                    visited.insert(state);
                }
            }
            step ++;
        }
        return -1;
    }
};

反思

当时一直想用二位坐标加步数表示状态,所以一直思考不出来。原因时在写二维题目时,习惯性只考虑坐标和目标这两个元素。所以要多分析题目中的新元素,对比与经验中的不同点。

相关推荐
aaaweiaaaaaa16 分钟前
蓝桥杯c ++笔记(含算法 贪心+动态规划+dp+进制转化+便利等)
c语言·数据结构·c++·算法·贪心算法·蓝桥杯·动态规划
Hesse20 分钟前
希尔排序:Python语言实现
python·算法
h^hh1 小时前
pipe匿名管道实操(Linux)
数据结构·c++·算法
dr李四维1 小时前
解决缓存穿透的布隆过滤器与布谷鸟过滤器:谁更适合你的应用场景?
redis·算法·缓存·哈希算法·缓存穿透·布隆过滤器·布谷鸟过滤器
亓才孓1 小时前
[leetcode]01背包问题
算法·leetcode·职场和发展
学习编程的gas2 小时前
数据结构——二叉树
数据结构·算法
its_a_win2 小时前
蓝桥杯 2023省B 飞机降落 dfs
c++·算法·蓝桥杯
MarvinP3 小时前
python基础:位置互换
开发语言·python·算法
zhglhy3 小时前
随机森林与决策树
算法·决策树·随机森林
BFT白芙堂3 小时前
Franka 机器人x Dexterity Gen引领遥操作精细任务新时代
人工智能·算法·机器学习·具身智能·franka机器人·科研机器人·机器人解决方案