LeetCode广搜深搜专题
原创
©著作权归作者所有:来自51CTO博客作者BugMaker999的原创作品,请联系作者获取转载授权,否则将追究法律责任
一、岛屿最大的面积(dfs)
这题是可以被一步捕获的棋子数的升级版,“可以被一步捕获的棋子数”只是从一个点开始遍历,而本题把所有的点都作为起始点遍历
- 深度优先遍历,选定一个点后不断地从上下左右四个方向遍历,直到某个方向碰到0或越界则返回
- 将已经遍历的点置为0,后面再递归遍历时就不会将此点重复计数
int visit(vector<vector<int>>& grid, int init_x, int init_y){
//这里判断时要注意,一定先判断是否越界,再判断值是否为0,否则报错看不出来,我就找了好长时间。。。
if(init_x<0 || init_y<0 || init_x > grid.size()-1 || init_y > grid[0].size()-1 || grid[init_x][init_y]==0 ){
return 0;
}
int count = 1;
grid[init_x][init_y] = 0;//将当前访问的区域置0
int x_d[4] = {0, 0, -1, 1};//这里用数组比用vector快
int y_d[4] = {1, -1, 0, 0};
for(int d = 0; d < 4; d++){//开始往4个方向遍历,直到碰到0或越界
count += visit(grid, init_x + x_d[d], init_y + y_d[d]);
}
return count;
}
int maxAreaOfIsland(vector<vector<int>>& grid) {
int res = 0;
for(int i=0; i<grid.size(); i++){
for(int j=0; j<grid[0].size(); j++){
int temp = visit(grid, i, j);//每个点都作为初始点,开始遍历
res = max(res , temp);
}
}
return res;
}
二、腐烂的橘子(bfs)
主要思路:
- 遍历二维数组,将已经腐烂的橘子的位置入队列
- 不断地从队列中取出橘子,分别试探上下左右四个方向是否有新鲜橘子,有则将其腐烂,并存入队列
- 由于队列中的橘子腐烂向外蔓延是同时发生的,我们每次将此时队列腐烂橘子数量记下,用一个for循环一次性处理完
BFS的代码框架(伪代码):
while (!queue.empty()){
cur_node = queue.front();
for(node : cur_node的相邻结点){
if(node没被访问过){
queue.push(node);
}
}
}
如果用上面这种写法来遍历,我们是无法区分 BFS 遍历中的每一“层”的。这是因为,遍历的时候,第 1 层的结点还没出完队列,第 2 层的结点就进来了。这个队列中第 1 层和第 2 层的结点会紧挨在一起,无法区分,也就无法知道当前正在腐烂的橘子所经历的时间time。
因此,我们需要修改一下代码,在每一层遍历开始前,记录队列中的结点数量 n ,然后一口气处理完这一层的 n 个结点 。然后遍历下一层的时候,把变量 time 加一
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int fresh = 0;
queue<pair<int,int>> roted;
for(int i=0; i<grid.size(); i++){
for(int j=0; j<grid[0].size(); j++){
if(grid[i][j] == 1){
fresh++;//记录新鲜橘子数量
}else if(grid[i][j] == 2){
roted.push(pair<int,int>(i,j));//存放腐烂橘子的坐标
}
}
}
int x_d[4] = {1,-1,0,0};
int y_d[4] = {0,0,1,-1};
int time = 0;
while(!roted.empty()){
int n = roted.size();//为方便记下访问的轮数,记下此时队列中的橘子数,一次访问完
bool isRoted = false;
for(int i=0; i<n; i++){//一轮循环访问队列中一个橘子的相邻结点
int cur_x = roted.front().first;
int cur_y = roted.front().second;
roted.pop();
for(int d=0; d<4; d++){
if(cur_x+x_d[d] >= grid.size() || cur_x+x_d[d] < 0 || cur_y+y_d[d] >= grid[0].size() || cur_y+y_d[d] < 0){
continue;
}
if(grid[cur_x+x_d[d]][cur_y+y_d[d]] == 1){//遇到新鲜橘子
grid[cur_x+x_d[d]][cur_y+y_d[d]] = 2;
roted.push(pair<int,int>(cur_x+x_d[d],cur_y+y_d[d]));
fresh--;//新鲜橘子减1
isRoted = true;
}
}
}
if(isRoted) time++;//本轮有新鲜橘子腐烂
else break;//本轮没有新鲜橘子腐烂,表示不会再有橘子腐烂了,此时队列也为空,直接跳出
}
return fresh == 0 ? time : -1;
}
};
三、地图分析(bfs)
个人认为解这题最关键的就是理解“曼哈顿距离”以及题目中描述的“离它最近的陆地距离是最大的”这两个描述
- 由曼哈顿距离的公式可知,此距离就是一个人在格子中只能往上下左右四个方向走(广搜每次只能搜索相邻的格子,这就和橘子腐烂的问题一样了),从一个格子走到另一个格子所走的次数
- 我们从陆地出发,搜索海洋。每次将搜索到的海洋置为陆地,入队列,距离加 1。下次从新入队列的陆地开始向相邻的结点搜索,没有新的陆地进入队列,无法向前搜索,此时得到的距离就是所需要求得“最大距离”
BFS 可以看成是层序遍历。从某个结点出发,BFS 首先遍历到距离为 1 的结点,然后是距离为 2、3、4…… 的结点。因此,BFS 可以用来求最短路径问题。BFS 先搜索到的结点,一定是距离最近的结点。反之,BFS 最后搜索到的结点,就是距离最远的结点
举例
海洋和陆地的初始状态如下:
搜索过程如下:
其实这题思路转换一下,和橘子腐烂问题一模一样,连代码都一样
int maxDistance(vector<vector<int>>& grid) {
queue <pair<int, int>> lands;
for(int i=0; i<grid.size(); i++){
for(int j=0; j<grid[0].size(); j++){
if(grid[i][j] == 1){
lands.push(pair<int,int>(i,j));//所有陆地入队列,作为第一层结点9
}
}
}
if(lands.size() == grid.size()*grid[0].size() || lands.size() == 0){
return -1;
}
int x_d[4] = {1,-1,0,0};
int y_d[4] = {0,0,1,-1};
int dis = 0;
while(!lands.empty()){
bool new_land = false;
int n = lands.size();
for(int i=0; i<n; i++){
int cur_x = lands.front().first;
int cur_y = lands.front().second;
lands.pop();
for(int d=0; d<4; d++){
int x = cur_x+x_d[d];//此时访问的格子坐标
int y = cur_y+y_d[d];
if(x<0 || x>=grid.size() || y<0 || y>=grid[0].size() ){//越界
continue;//此方向越界,继续下一次循环
}
if(grid[x][y] == 0){//遇到海洋
grid[x][y] = 1;
lands.push(pair<int,int>(x,y));
new_land = true;//表示有新的陆地入队,需要再搜索一轮
}
}
}
if(new_land) dis++;
else break;
}
return dis;
}