LeetCode·每日一题·864.获取所有钥匙的最短路径·广度优先搜索

作者:小迅
链接:https://leetcode.cn/problems/shortest-path-to-get-all-keys/solutions/1960590/yan-du-you-xian-sou-suo-zhu-shi-chao-ji-75r4k/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 

题目

 

示例

 

思路

正常的广度优先遍历只需要使用一个数组充当队列,并且用入参获取迭代结果就可以了,但是这个题需要记录的东西太多了。思路:

  • 想要走迷宫,最先想到的方法就是广度或者深度优先遍历,因为本题的目的是求最短路径,所以直观上广度更合适,满足条件后迭代的次数就是距离了;
  • 满足条件,是什么条件?不是走出迷宫,因为没有出口,也不是打开所有锁,而是收集齐所有的钥匙,所以我先遍历了一下数组,找到了钥匙的数量,接下来只要某次迭代获取的钥匙数量已经和这个值相同了,那就可以返回了,否则不能再迭代下去说明没有结果,返回-1。另外不知道有没有相关用例,但是因为没有说@的坐标就是(0,0),我在遍历过程中还获取了一下符号@的坐标作为起始点;
  • 那下面的难点就在怎么收集钥匙了,用广度优先遍历,每次遍历就相当于一种新的解法,为了防止无限循环,那就需要标记走过的路,可以在原来的数组上标记吗?不可以,因为多次选择路径必须分别记录,二维数组是满足不了这个需求的,至少要三维,其中两个维度依旧是坐标,剩下的维度实际上是钥匙的获取状态,一共6个钥匙,以一个整型变量的低6位记录状态,那不同的状态组合最多有2^6 - 1 = 63个,那么标记数组的最后一维大小就可以设置为63。
  • 那么接下来就是遍历的过程了:
    • 先将@符号入队,同时将什么符号都没有找到时的状态的@的坐标标记为1;
    • 开始将@上下左右的合法的节点入队,没有碰到钥匙的时候状态不变,无法倒退,但是一旦找到了一个钥匙,那对应的钥匙状态就改变了,可以倒退了;
    • 在面对不同的符号时操作有所不同,具体看注释吧;
    • 如果在某次迭代时发现已经找到了所有的钥匙,那就停止遍历,返回距离;但是如果发现没有新的节点入队,即队列为空了,就退出返回-1。 

代码


// 位运算宏,包括获取某一位的值和设置某一位的值
#define	GET_BIT(x, bit)	(((x) & (1 << (bit))) >> (bit))
#define	SET_BIT(x, bit)	((x) |= (1 << (bit)))
// 用于快速获取上下左右坐标
int g_index[][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
// 用于统计钥匙数量,获取开始节点坐标,同时在队列节点里要记录当前找到的钥匙状态和路径长度
int *getKeyNumAndStart(char ** grid, int gridSize, int colSize, int *keyNum)
{
    int *startIndex = (int *)malloc(sizeof(int) * 4);
    for (int i = 0; i < gridSize; i++) {
        for (int j = 0 ; j < colSize; j++) {
            if (grid[i][j] >= 'a' && grid[i][j] <= 'f') {
                (*keyNum)++;
            }
            if (grid[i][j] == '@') {
                startIndex[0] = i;  // 开始节点坐标
                startIndex[1] = j;
                startIndex[2] = 0;  // 钥匙状态,都没找到就是0
                startIndex[3] = 0;  // 路径,一开始也是0
            }
        }
    }
    return startIndex;
}
// 获取当前已经找到的钥匙数量,需要统计低六位的1的数量
int getCurKeyNum(int nodeKey)
{
    int keyNum = 0;
    for (int i = 0; i < 6; i++) {
        int num = GET_BIT(nodeKey, i);
        keyNum += num;
    }
    return keyNum;
}
// 广度优先遍历
void BFS(char ** grid, int **arrList, int start, int end, int keyNum, int *minPath, int ***visList,
         int gridSize, int colSize)
{
    int startTemp = start;
    int endTemp = end;
    // 如果发现没有新的节点入队,即队列为空了,就退出返回-1
    if (start >= end) {
        return;
    }
    // 对该层节点进行遍历
    for (int i = startTemp; i < endTemp; i++) {
        // 获取一个节点
        int *tempNode = arrList[i];
        // 如果发现已经找到了所有钥匙,直接返回距离即可
        int curKeyNum = getCurKeyNum(tempNode[2]);
        if (curKeyNum == keyNum) {
            *minPath = tempNode[3];
            return;
        }
        // 否则继续
        for (int j = 0; j < 4 ; j++) {
            int x_temp = tempNode[0] + g_index[j][0];
            int y_temp = tempNode[1] + g_index[j][1];
            // 如果当前的节点坐标存在越界或者已经遍历过了,就跳过
            if (x_temp < 0 || y_temp < 0 || x_temp >= gridSize || y_temp >= colSize ||
                visList[x_temp][y_temp][tempNode[2]] == 1) {
                continue;
            }
            // 遇到 ‘#’ 就标记遍历过就行了,实际不做操作就行,标记其实应该都没必要
            if (grid[x_temp][y_temp] == '#') {
                visList[x_temp][y_temp][tempNode[2]] = 1;
            }
            // 遇到 “.” 或者“@”时(注意:有可能是反向跑回到起始点的,比如有的钥匙可能要绕一圈)
            // 将节点入队,并标记标记数组中的对应位置
            if (grid[x_temp][y_temp] == '.' || grid[x_temp][y_temp] == '@') {
                visList[x_temp][y_temp][tempNode[2]] = 1;
                arrList[end] = (int *)malloc(sizeof(int) * 4);
                arrList[end][0] = x_temp;
                arrList[end][1] = y_temp;
                arrList[end][2] = tempNode[2];
                arrList[end][3] = tempNode[3] + 1;
                end++;
            }
            // 遇到钥匙时,还是要标记一下当前状态的标记数组对应位置,但是状态马上就会改变;更新状态后将节点入队
            if (grid[x_temp][y_temp] >= 'a' && grid[x_temp][y_temp] <= 'f') {
                visList[x_temp][y_temp][tempNode[2]] = 1;
                arrList[end] = (int *)malloc(sizeof(int) * 4);
                arrList[end][0] = x_temp;
                arrList[end][1] = y_temp;
                int temp = tempNode[2];
                SET_BIT(temp, grid[x_temp][y_temp] - 'a');
                arrList[end][2] = temp;
                arrList[end][3] = tempNode[3] + 1;
                end++;
            }
            // 遇到锁时,如果没钥匙,不做操作,如果有钥匙,就标记并入队
            if (grid[x_temp][y_temp] >= 'A' && grid[x_temp][y_temp] <= 'F') {
                int temp = tempNode[2];
                if (GET_BIT(temp, grid[x_temp][y_temp] - 'A') == 1) {
                    visList[x_temp][y_temp][tempNode[2]] = 1;
                    arrList[end] = (int *)malloc(sizeof(int) * 4);
                    arrList[end][0] = x_temp;
                    arrList[end][1] = y_temp;
                    arrList[end][2] = tempNode[2];
                    arrList[end][3] = tempNode[3] + 1;
                    end++;
                }
            }
        }
        // 遍历过的节点出队
        start++;
    }
    // 继续下一层的广度优先遍历
    BFS(grid, arrList, start, end, keyNum, minPath, visList, gridSize, colSize);
    return;
}
int shortestPathAllKeys(char ** grid, int gridSize){
    int colSize = strlen(grid[0]);
    int keyNum = 0;
    // visList是标记数组,前两个维度是x,y轴坐标,后一个维度是钥匙状态,注意初始化
    int ***visList = (int ***)malloc(sizeof(int **) * gridSize);
    memset(visList, 0, sizeof(int **) * gridSize);
    for (int i = 0; i < gridSize; i++) {
        visList[i] = (int **)malloc(sizeof(int *) * colSize);
        memset(visList[i], 0, sizeof(int *) * colSize);
        for (int j = 0; j < colSize; j++) {
            visList[i][j] = (int *)malloc(sizeof(int) * 63);
            memset(visList[i][j], 0, sizeof(int) * 63);
        }
    }
    // 充当队列的数组,数组元素包括坐标、当前找到的钥匙状态和走过的路径长度
    int **arrList = (int **)malloc(sizeof(int *) * gridSize * colSize * 63);
    memset(arrList, 0, sizeof(int *) * gridSize * colSize * 63);
    // 获取开始节点的位置等信息
    arrList[0] = getKeyNumAndStart(grid, gridSize, colSize, &keyNum);
    // 初始化返回值
    int minPath = -1;
    // 标记数组中的首节点设为1
    visList[arrList[0][0]][arrList[0][1]][arrList[0][2]] = 1;
    // 开始广度优先遍历
    BFS(grid, arrList, 0, 1, keyNum, &minPath, visList, gridSize, colSize);

    return minPath;
}



作者:小迅
链接:https://leetcode.cn/problems/shortest-path-to-get-all-keys/solutions/1960590/yan-du-you-xian-sou-suo-zhu-shi-chao-ji-75r4k/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值