1.前言
從該題學到了多源廣度優先搜索BFS,stl中queue,以及pair的用法,語言爲C++
2.題目描述
難度:簡單
在給定的網格中,每個單元格可以有以下三個值之一:
值 0 代表空單元格;
值 1 代表新鮮橘子;
值 2 代表腐爛的橘子。
每分鐘,任何與腐爛的橘子(在 4 個正方向上)相鄰的新鮮橘子都會腐爛。
返回直到單元格中沒有新鮮橘子爲止所必須經過的最小分鐘數。如果不可能,返回 -1。
示例 1:
輸入:[[2,1,1],[1,1,0],[0,1,1]]
輸出:4
示例 2:
輸入:[[2,1,1],[0,1,1],[1,0,1]]
輸出:-1
解釋:左下角的橘子(第 2 行, 第 0 列)永遠不會腐爛,因爲腐爛只會發生在 4 個正向上。
示例 3:
輸入:[[0,2]]
輸出:0
解釋:因爲 0 分鐘時已經沒有新鮮橘子了,所以答案就是 0 。
提示:
1 <= grid.length <= 10
1 <= grid[0].length <= 10
grid[i][j] 僅爲 0、1 或 2
通過次數20,119提交次數39,759
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/rotting-oranges
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
3.自己解
沒有想到用BFS,找到所有新鮮橘子,尋找周圍是否有懷橘子,有——變壞;沒有——保持;
class Solution {
public:
int find1(vector<vector<int>> &grid,int x,int y,int xn,int yn)
{//檢測所有的1周圍是否有2(返回2)、或10(返回1)、或只有0(返回0)
vector<int> num(4,-1);//遇到牆壁不賦值,爲-1
if(x-1>=0)//上
num[0]=grid[x-1][y];
if(x+1<xn)//下
num[1]=grid[x+1][y];
if(y-1>=0)//左
num[2]=grid[x][y-1];
if(y+1<yn)//右
num[3]=grid[x][y+1];
int flag=-1;//通過flag記錄1周圍點的最大值,就可以判斷時下列三種情況的哪一種
for(int i=0;i<4;i++)//檢測所有的1周圍是否有2(返回2)、或10(返回1)、或只有0(返回0)
{
if(num[i]>flag)
flag=num[i];
}
return flag;
}
int orangesRotting(vector<vector<int>>& grid) {
int num1 = 0;
int num2 = 0;//記錄grid中有多少1和2
int xn=grid.size();
int yn=grid[0].size();
int i,j;
//存在1,周圍全是0,返回-1,沒有2,也不會發生腐爛,返回-1;grid裏面一個1也沒有,返回0;
for(i = 0;i<xn;i++)
{
for(j=0;j<yn;j++)
{
if(grid[i][j]==1)
{
if(find1(grid,i,j,xn,yn)==0)
return -1;//存在1,周圍全是0,返回-1
else
num1++;
}
else if(grid[i][j]==2)
num2++;
}
}
if(num1==0) return 0;//grid裏面一個1也沒有,返回0;
if(num2==0) return -1;//沒有2,也不會發生腐爛,返回-1
int count=0;//end=1結束的標誌是grid中沒有1了,即num1==0
vector<vector<int>> temp(grid.begin(),grid.end());//輔助空間,每次傳播一次更新一次
while(num1>0)//此時每個1周圍要麼有2,要麼沒有2,附近只有0,1;
{
int change=0;//每次循環都有橘子壞掉,若沒有壞掉且有新鮮橘子num1>0,說明存在不連通的子圖
for(i = 0;i<xn;i++)
{
for(j=0;j<yn;j++)
{
if(grid[i][j]==1)
{
if(find1(grid,i,j,xn,yn)==2)
{
temp[i][j]=2;
num1--;
change=1;
}
}
}
}
if(change == 0)//沒有橘子壞掉,存在不連通的子圖
return -1;
count++;
grid.clear();
for(i=0;i<xn;i++)
{
grid.push_back(temp[i]);
}//grid一輪傳播後更新
}
return count;
}
};
執行結果
自己寫的方法比較笨
4.官方解
方法一:枚舉 + 廣度優先搜索
思路
由題目我們可以知道每分鐘每個腐爛的橘子都會使上下左右相鄰的新鮮橘子腐爛,這其實是一個模擬廣度優先搜索的過程。所謂廣度優先搜索,就是從起點出發,每次都嘗試訪問同一層的節點,如果同一層都訪問完了,再訪問下一層,最後廣度優先搜索找到的路徑就是從起點開始的最短合法路徑。
回到題目中,假設圖中只有一個腐爛的橘子,它每分鐘向外拓展,腐爛上下左右相鄰的新鮮橘子,那麼下一分鐘,就是這些被腐爛的橘子再向外拓展腐爛相鄰的新鮮橘子,這與廣度優先搜索的過程均一一對應,上下左右相鄰的新鮮橘子就是該腐爛橘子嘗試訪問的同一層的節點,路徑長度就是新鮮橘子被腐爛的時間。我們記錄下每個新鮮橘子被腐爛的時間,最後如果單元格中沒有新鮮橘子,腐爛所有新鮮橘子所必須經過的最小分鐘數就是新鮮橘子被腐爛的時間的最大值。
以上是基於圖中只有一個腐爛的橘子的情況,可實際題目中腐爛的橘子數不止一個,看似與廣度優先搜索有所區別,不能直接套用,但其實有兩個方向的思路。
一個是耗時比較大且不推薦的做法:我們對每個腐爛橘子爲起點都進行一次廣度優先搜索,用 dis[x][y][i]dis[x][y][i] 表示只考慮第 ii 個腐爛橘子爲起點的廣度優先搜索,座標位於 (x, y)(x,y) 的新鮮橘子被腐爛的時間,設沒有被腐爛的新鮮橘子的 dis[x][y][i]=infdis[x][y][i]=inf ,即無限大,表示沒有被腐爛,那麼每個新鮮橘子被腐爛的最短時間即爲
min_{i}\ dis[x][y][i]
min
i
dis[x][y][i]
最後的答案就是所有新鮮橘子被腐爛的最短時間的最大值,如果是無限大,說明有新鮮橘子沒有被腐爛,輸出 -1−1 即可。
無疑上面的方法需要枚舉每個腐爛橘子,所以時間複雜度需要在原先廣度優先搜索遍歷的時間複雜度上再乘以腐爛橘子數,這在整個網格範圍變大的時候十分耗時,所以需要另尋他路。
方法二:多源廣度優先搜索
思路
觀察到對於所有的腐爛橘子,其實它們在廣度優先搜索上是等價於同一層的節點的。
假設這些腐爛橘子剛開始是新鮮的,而有一個腐爛橘子(我們令其爲超級源點)會在下一秒把這些橘子都變腐爛,而這個腐爛橘子剛開始在的時間是 -1−1 ,那麼按照廣度優先搜索的算法,下一分鐘也就是第 00 分鐘的時候,這個腐爛橘子會把它們都變成腐爛橘子,然後繼續向外拓展,所以其實這些腐爛橘子是同一層的節點。那麼在廣度優先搜索的時候,我們將這些腐爛橘子都放進隊列裏進行廣度優先搜索即可,最後每個新鮮橘子被腐爛的最短時間 dis[x][y]dis[x][y] 其實是以這個超級源點的腐爛橘子爲起點的廣度優先搜索得到的結果。
爲了確認是否所有新鮮橘子都被腐爛,可以記錄一個變量 cntcnt 表示當前網格中的新鮮橘子數,廣度優先搜索的時候如果有新鮮橘子被腐爛,則 cnt-=1 ,最後搜索結束時如果 cntcnt 大於 00 ,說明有新鮮橘子沒被腐爛,返回 -1−1 ,否則返回所有新鮮橘子被腐爛的時間的最大值即可,也可以在廣度優先搜索的過程中把已腐爛的新鮮橘子的值由 1 改爲 2,最後看網格中是否由值爲 1 即新鮮的橘子即可。
採用方法2,源代碼爲
class Solution {//多源廣度優先搜索
int cnt;//新鮮橘子數
int dis[10][10];//座標位於x,y的橘子的腐爛時間<=>距離
int dir_x[4]={0, 1, 0, -1};
int dir_y[4]={1, 0, -1, 0};//方向向量,右,下,左,上
public:
int orangesRotting(vector<vector<int>>& grid) {
queue<pair<int,int> >Q;//腐爛的橘子進入隊列進行廣度優先搜索,這裏存的是腐爛橘子的座標
memset(dis, -1, sizeof(dis));//假設所有橘子都是新鮮的,-1,即離腐爛橘子無窮遠
cnt = 0;
int n=(int)grid.size(), m=(int)grid[0].size(), ans = 0;//n行m列,ans是傳播完所有橘子的時間
for (int i = 0; i < n; ++i){
for (int j = 0; j < m; ++j){
if (grid[i][j] == 2){
Q.push(make_pair(i, j));
dis[i][j] = 0;//0時刻,污染的橘子進隊列,初始化
}
else if (grid[i][j] == 1) cnt += 1;//統計新鮮橘子
}
}
while (!Q.empty()){
pair<int,int> x = Q.front();Q.pop();//取隊首並彈出
//從污染的橘子開始進行一輪廣度優先搜索
for (int i = 0; i < 4; ++i){//方向向量,右,下,左,上
int tx = x.first + dir_x[i];
int ty = x.second + dir_y[i];
if (tx < 0|| tx >= n || ty < 0|| ty >= m|| ~dis[tx][ty] || !grid[tx][ty]) continue;
//~(-1)=0,其餘數字取反不等於0,即~dis[tx][ty]等價於dis[tx][ty]!=-1
//不進行下列代碼的條件,越界、撞牆即grid=0、探索過的橘子dis[tx][ty]!=-1
dis[tx][ty] = dis[x.first][x.second] + 1;//這一輪被搜索到的橘子是上一輪橘子距離加一
Q.push(make_pair(tx, ty));//被污染的橘子進隊列
if (grid[tx][ty] == 1){//如果是新鮮橘子,則數量-1,答案更新
cnt -= 1;
ans = dis[tx][ty];
if (!cnt) break;//如果新鮮橘子數量=0,結束搜索
}
}
}
//cnt>1,則說明有新鮮橘子,返回-1;cnt==0,說明沒有新鮮橘子,返回ans
return cnt ? -1 : ans;
}
};
運行結果
還行吧
思路畫成流程圖爲