今天,我们继续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)尽可能少。移动时可以向上、下、左、右四个方向移动,但不能走出迷宫的边界。
数据结构选择
- 网格表示:使用二维数组
grid来表示迷宫,其中grid[i][j]表示第i行第j列的格子是黑色(1)还是白色(0)。 - 记录最少黑色格子数量:使用另一个二维数组
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;
}
算法步骤
-
初始化:
- 定义方向数组
directions,表示上下左右四个方向的移动。 - 初始化
minBlack数组,将起点(0,0)的值设为grid[0][0]。
- 定义方向数组
-
广度优先搜索(BFS):
- 使用队列
bfsQueue进行 BFS 搜索,从起点(0,0)开始。 - 对于当前格子
(currentX, currentY),遍历四个方向,计算新位置(newX, newY)。 - 如果新位置在边界内,并且通过当前路径到达新位置所需经过的黑色格子数量更少,则更新
minBlack[newX][newY]并将其加入队列。
- 使用队列
-
返回结果:
- BFS 结束后,
minBlack[n-1][m-1]即为从起点到终点所需经过的最少黑色格子数量。
- BFS 结束后,
复杂度分析
- 时间复杂度:O(n * m),因为每个格子最多被访问一次。
- 空间复杂度:O(n * m),用于存储
minBlack数组和 BFS 队列。
总结
通过 BFS 算法,我们可以有效地找到从起点到终点的最少黑色格子路径。关键在于使用 minBlack 数组记录每个格子的最少黑色格子数量,并通过队列进行广度优先搜索,确保每个格子只被访问一次。
老问题,还可以再优化吗?
当然可以!我们可以通过以下几个方面来优化你的代码:
- 减少不必要的头文件:只包含必要的头文件。
- 使用更简洁的变量名:使代码更易读。
- 优化 BFS 的初始化:避免不必要的初始化操作。
- 使用更高效的队列操作:减少不必要的拷贝操作。
优化后的代码
#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;
}
优化点解释
-
减少不必要的头文件:
- 只保留了必要的头文件
#include <iostream>,#include <vector>,#include <queue>,#include <utility>,#include <climits>。
- 只保留了必要的头文件
-
使用更简洁的变量名:
- 将
currentX和currentY简化为x和y,使代码更易读。
- 将
-
优化 BFS 的初始化:
- 初始化
minBlack数组时,直接将起点(0,0)的值设为grid[0][0],避免了不必要的初始化操作。
- 初始化
-
使用更高效的队列操作:
- 使用
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;
}
优化点解释
-
使用
std::priority_queue:- 使用
std::priority_queue来优先处理经过黑色格子数量最少的路径。这样可以确保每次扩展的路径都是当前最优的,从而减少不必要的扩展。
- 使用
-
减少不必要的扩展:
- 在 BFS 过程中,如果当前路径的黑色格子数量已经大于等于记录的最小值,则跳过该路径,避免不必要的扩展。
今天的学习就到这里!