青训营X豆包MarsCode 技术训练营第三课 | 豆包MarsCode AI 刷题

54 阅读7分钟

今天,我们继续MarsCode的刷题之旅。 挑战一下困难题。

问题描述

小S在一个n×mn×m的网格迷宫中,初始位置在左上角 (1,1)(1,1),目标是到达右下角 (n,m)(n,m)。每个格子可以是黑色(表示为1)或者白色(表示为0)。他希望在移动过程中经过的黑色格子尽可能少。移动时可以向上、下、左、右四个方向移动,但不能走出迷宫的边界。请你帮小S计算从起点到终点所需经过的最少黑色格子的数量。


测试样例

样例1:

输入:n = 5 ,m = 3 ,grid = [[0, 1, 0], [0, 1, 1], [0, 1, 0], [1, 0, 0], [1, 0, 0]]
输出:1

样例2:

输入:n = 4 ,m = 4 ,grid = [[0, 0, 1, 0], [1, 0, 1, 0], [1, 0, 0, 0], [1, 1, 1, 0]]
输出:0

样例3:

输入:n = 3 ,m = 3 ,grid = [[0, 0, 0], [1, 1, 0], [1, 1, 0]]
输出:0

我们可以呼叫MarsCode AI 给我们一些提示

题目分析

问题理解

题目要求在一个 n x m 的网格迷宫中,从左上角 (1,1) 移动到右下角 (n,m),并且希望在移动过程中经过的黑色格子(表示为 1)尽可能少。移动时可以向上、下、左、右四个方向移动,但不能走出迷宫的边界。

数据结构选择

  1. 网格表示:使用二维数组 grid 来表示迷宫,其中 grid[i][j] 表示第 i 行第 j 列的格子是黑色(1)还是白色(0)。
  2. 记录最少黑色格子数量:使用另一个二维数组 minBlack 来记录从起点到每个格子所需经过的最少黑色格子数量。初始时,所有格子的值设为 INT_MAX,表示尚未访问过。

使用BFS解决该问题

代码如下:

#include #include #include #include #include #include #include using namespace std;

int solution(int n, int m, std::vector<std::vector>& grid) { // 定义方向数组, 表示上下左右四个方向 vector<pair<int,int>> directions = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

// 创建二维数组, 记录每个点所需经过的最少黑色格子数量
vector<vector<int>> minBlack(n, vector<int>(m, INT_MAX));
minBlack[0][0] = grid[0][0];

// 创建队列进行 BFS 搜索
queue<pair<int,int>> bfsQueue;
bfsQueue.push({0, 0}); // 从起点开始

while (!bfsQueue.empty()) {
    auto [currentX, currentY] = bfsQueue.front();
    bfsQueue.pop();

    // 遍历四个方向
    for (const auto& [dx, dy] : directions) {
        int newX = currentX + dx;
        int newY = currentY + dy;

        // 确保新位置在边界内
        if (newX >= 0 && newX < n && newY >= 0 && newY < m) {
            // 计算新的黑色格子数量
            int newBlackCount = minBlack[currentX][currentY] + grid[newX][newY];

            // 如果新的路径经过的黑色格子数量更少, 则更新并加入队列
            if (newBlackCount < minBlack[newX][newY]) {
                minBlack[newX][newY] = newBlackCount;
                bfsQueue.push({newX, newY});
            }
        }
    }
}

return minBlack[n-1][m-1]; // 返回右下角所需经过的最少黑色格子数量

}

int main() { std::vector<std::vector> v1 = {{0, 1, 0}, {0, 1, 1}, {0, 1, 0}, {1, 0, 0}, {1, 0, 0}}; std::vector<std::vector> v2 = {{0, 0, 1, 0}, {1, 0, 1, 0}, {1, 0, 0, 0}, {1, 1, 1, 0}}; std::vector<std::vector> v3 = {{0, 0, 0}, {1, 1, 0}, {1, 1, 0}};

std::cout << (solution(5, 3, v1) == 1) << std::endl; // 输出 1
std::cout << (solution(4, 4, v2) == 0) << std::endl; // 输出 0
std::cout << (solution(3, 3, v3) == 0) << std::endl; // 输出 0

return 0;

}

算法步骤

  1. 初始化

    • 定义方向数组 directions,表示上下左右四个方向的移动。
    • 初始化 minBlack 数组,将起点 (0,0) 的值设为 grid[0][0]
  2. 广度优先搜索(BFS)

    • 使用队列 bfsQueue 进行 BFS 搜索,从起点 (0,0) 开始。
    • 对于当前格子 (currentX, currentY),遍历四个方向,计算新位置 (newX, newY)
    • 如果新位置在边界内,并且通过当前路径到达新位置所需经过的黑色格子数量更少,则更新 minBlack[newX][newY] 并将其加入队列。
  3. 返回结果

    • BFS 结束后,minBlack[n-1][m-1] 即为从起点到终点所需经过的最少黑色格子数量。

复杂度分析

  • 时间复杂度:O(n * m),因为每个格子最多被访问一次。
  • 空间复杂度:O(n * m),用于存储 minBlack 数组和 BFS 队列。

总结

通过 BFS 算法,我们可以有效地找到从起点到终点的最少黑色格子路径。关键在于使用 minBlack 数组记录每个格子的最少黑色格子数量,并通过队列进行广度优先搜索,确保每个格子只被访问一次。

老问题,还可以再优化吗?

当然可以!我们可以通过以下几个方面来优化你的代码:

  1. 减少不必要的头文件:只包含必要的头文件。
  2. 使用更简洁的变量名:使代码更易读。
  3. 优化 BFS 的初始化:避免不必要的初始化操作。
  4. 使用更高效的队列操作:减少不必要的拷贝操作。

优化后的代码

#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <climits>

using namespace std;

int solution(int n, int m, vector<vector<int>>& grid) {
    // 定义方向数组, 表示上下左右四个方向
    vector<pair<int, int>> directions = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

    // 创建二维数组, 记录每个点所需经过的最少黑色格子数量
    vector<vector<int>> minBlack(n, vector<int>(m, INT_MAX));
    minBlack[0][0] = grid[0][0];

    // 创建队列进行 BFS 搜索
    queue<pair<int, int>> bfsQueue;
    bfsQueue.push({0, 0}); // 从起点开始

    while (!bfsQueue.empty()) {
        auto [x, y] = bfsQueue.front();
        bfsQueue.pop();

        // 遍历四个方向
        for (const auto& [dx, dy] : directions) {
            int newX = x + dx;
            int newY = y + dy;

            // 确保新位置在边界内
            if (newX >= 0 && newX < n && newY >= 0 && newY < m) {
                // 计算新的黑色格子数量
                int newBlackCount = minBlack[x][y] + grid[newX][newY];

                // 如果新的路径经过的黑色格子数量更少, 则更新并加入队列
                if (newBlackCount < minBlack[newX][newY]) {
                    minBlack[newX][newY] = newBlackCount;
                    bfsQueue.push({newX, newY});
                }
            }
        }
    }

    return minBlack[n-1][m-1]; // 返回右下角所需经过的最少黑色格子数量
}

int main() {
    vector<vector<int>> v1 = {{0, 1, 0}, {0, 1, 1}, {0, 1, 0}, {1, 0, 0}, {1, 0, 0}};
    vector<vector<int>> v2 = {{0, 0, 1, 0}, {1, 0, 1, 0}, {1, 0, 0, 0}, {1, 1, 1, 0}};
    vector<vector<int>> v3 = {{0, 0, 0}, {1, 1, 0}, {1, 1, 0}};

    cout << (solution(5, 3, v1) == 1) << endl; // 输出 1
    cout << (solution(4, 4, v2) == 0) << endl; // 输出 0
    cout << (solution(3, 3, v3) == 0) << endl; // 输出 0

    return 0;
}

优化点解释

  1. 减少不必要的头文件

    • 只保留了必要的头文件 #include <iostream>, #include <vector>, #include <queue>, #include <utility>, #include <climits>
  2. 使用更简洁的变量名

    • currentXcurrentY 简化为 xy,使代码更易读。
  3. 优化 BFS 的初始化

    • 初始化 minBlack 数组时,直接将起点 (0,0) 的值设为 grid[0][0],避免了不必要的初始化操作。
  4. 使用更高效的队列操作

    • 使用 auto [x, y] = bfsQueue.front(); 直接解构队列元素,减少了不必要的拷贝操作。

进一步优化

当然可以!我们可以进一步优化代码,使用更高效的数据结构来减少不必要的操作和内存使用。具体来说,我们可以使用 std::deque 来代替 std::queue,并且在 BFS 过程中使用 std::priority_queue 来优先处理经过黑色格子数量最少的路径。

优化后的代码

#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <climits>

using namespace std;

int solution(int n, int m, vector<vector<int>>& grid) {
    // 定义方向数组, 表示上下左右四个方向
    vector<pair<int, int>> directions = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

    // 创建二维数组, 记录每个点所需经过的最少黑色格子数量
    vector<vector<int>> minBlack(n, vector<int>(m, INT_MAX));
    minBlack[0][0] = grid[0][0];

    // 创建优先队列进行 BFS 搜索,优先处理经过黑色格子数量最少的路径
    priority_queue<pair<int, pair<int, int>>, vector<pair<int, pair<int, int>>>, greater<>> pq;
    pq.push({grid[0][0], {0, 0}}); // 从起点开始

    while (!pq.empty()) {
        auto [blackCount, pos] = pq.top();
        auto [x, y] = pos;
        pq.pop();

        // 如果当前路径的黑色格子数量已经大于等于记录的最小值,则跳过
        if (blackCount > minBlack[x][y]) continue;

        // 遍历四个方向
        for (const auto& [dx, dy] : directions) {
            int newX = x + dx;
            int newY = y + dy;

            // 确保新位置在边界内
            if (newX >= 0 && newX < n && newY >= 0 && newY < m) {
                // 计算新的黑色格子数量
                int newBlackCount = blackCount + grid[newX][newY];

                // 如果新的路径经过的黑色格子数量更少, 则更新并加入队列
                if (newBlackCount < minBlack[newX][newY]) {
                    minBlack[newX][newY] = newBlackCount;
                    pq.push({newBlackCount, {newX, newY}});
                }
            }
        }
    }

    return minBlack[n-1][m-1]; // 返回右下角所需经过的最少黑色格子数量
}

int main() {
    vector<vector<int>> v1 = {{0, 1, 0}, {0, 1, 1}, {0, 1, 0}, {1, 0, 0}, {1, 0, 0}};
    vector<vector<int>> v2 = {{0, 0, 1, 0}, {1, 0, 1, 0}, {1, 0, 0, 0}, {1, 1, 1, 0}};
    vector<vector<int>> v3 = {{0, 0, 0}, {1, 1, 0}, {1, 1, 0}};

    cout << (solution(5, 3, v1) == 1) << endl; // 输出 1
    cout << (solution(4, 4, v2) == 0) << endl; // 输出 0
    cout << (solution(3, 3, v3) == 0) << endl; // 输出 0

    return 0;
}

优化点解释

  1. 使用 std::priority_queue

    • 使用 std::priority_queue 来优先处理经过黑色格子数量最少的路径。这样可以确保每次扩展的路径都是当前最优的,从而减少不必要的扩展。
  2. 减少不必要的扩展

    • 在 BFS 过程中,如果当前路径的黑色格子数量已经大于等于记录的最小值,则跳过该路径,避免不必要的扩展。

今天的学习就到这里!