题目描述
描述:设想有个机器人坐在一个网格的左上角,网格 r 行 c 列。机器人只能向下或向右移动,但不能走到一些被禁止的网格(有障碍物)。设计一种算法,寻找机器人从左上角移动到右下角的路径。
网格中的障碍物和空位置分别用 1 和 0 来表示。
返回一条可行的路径,路径由经过的网格的行号和列号组成。左上角为 0 行 0 列。如果没有可行的路径,返回空数组。
示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: [[0,0],[0,1],[0,2],[1,2],[2,2]]
解释:
输入中标粗的位置即为输出表示的路径,即
0行0列(左上角) -> 0行1列 -> 0行2列 -> 1行2列 -> 2行2列(右下角)
说明:r 和 c 的值均不超过 100。
解题思路
思路1:最直观的想法是,递归判断是否存在从初始节点到目标节点的一条无障碍路径。使用m表示矩阵的行,使用n表示矩阵的列,使用visited表示矩阵单元格是否被访问过,使用res表示从初始节点到目标节点的路径数组,使用dfs(row,col,m,n,grid,res,visited)表示是否存在从(row,col)到目标节点的无障碍路径。dfs函数具体实现思路是:递归出口包含矩阵下标的合法性判断、障碍物判断、访问标志判断以及到达目标节点判断;递归主体包含三个部分:首先将坐标加入路径数组,同时置访问标志;然后判断是否存在(row,col+1)或者(row+1,col)到目标节点的一条无障碍路径,如果存在则返回true;最后是做回溯处理,即将坐标从路径数组弹出,再返回false表示从(row,col)到目标节点没有一条无障碍路径。此处需要注意,由于只能向下或者向右,故当发现从(row,col)到目标节点没有一条无障碍路径时,其他路径再次走到(row,col),其无论如何走也是都无法到达终点的,故此处不需要回溯访问标志。
bool dfs(int row,int col,int m,int n,vector<vector<int>>& obstacleGrid,vector<vector<int>>& res,vector<vector<bool>>& visited)
{
//坐标合法性判断以及是否有障碍物判断以及是否访问判断
if(row>=m||col>=n||obstacleGrid[row][col]==1||visited[row][col])
return false;
//到达终点
if(row==m-1&&col==n-1)
{
res.push_back({row,col});
return true;
}
//加入坐标
res.push_back({row,col});
//置访问标志
visited[row][col]=true;
//判断(row,col+1)或者(row+1,col)到终点是否存在一条路径 if(dfs(row,col+1,m,n,obstacleGrid,res,visited)||dfs(row+1,col,m,n,obstacleGrid,res,visited))
return true;
//没有则回溯弹出数组
res.pop_back();
//不需要置访问 因为如果当前某点向后走无法到达终点 那么其他点到达该点后仍是无法到达终点的
return false;
}
vector<vector<int>> pathWithObstacles(vector<vector<int>>& obstacleGrid) {
int m=obstacleGrid.size();
int n=obstacleGrid[0].size();
vector<vector<int>> res;
vector<vector<bool>> visited(m,vector<bool>(n,false));
//起点有障碍或者终点有障碍
if(obstacleGrid[0][0]==1||obstacleGrid[m-1][n-1]==1)
return res;
//判断从(0,0)到终点是否存在一条路径
dfs(0,0,m,n,obstacleGrid,res,visited);
return res;
}
思路2:动态规划判断是否存在从初始节点到目标节点的一条无障碍路径。使用m表示矩阵的行,使用n表示矩阵的列,使用res表示从初始节点到目标节点的路径数组,使用dp[i][j]表示坐标(i,j)是否可以到达。首先初始化dp[0][0]为true;接着分别初始化首行(0,i)和首列(i,0),(0,i)可以到达的条件是(0,i-1)可以到达且(0,i)无障碍物,(i,0)可以到达的条件是(i-1,0)可以到达且(i,0)无障碍物;然后计算(i,j)是否可达,(i,j)可达的条件是(i,j)无障碍物并且(i-1,j)或者(i,j-1)可达;最后判断(m-1,n-1)是否可达,如果不可达则返回空数组,反之可达则逆序加入路径,分别根据上一步是上方可达的情况还是上一步是左方可达的情况,将对应的坐标加入数组,最后再对数组进行逆序处理即可。
vector<vector<int>> pathWithObstacles(vector<vector<int>>& obstacleGrid) {
int m=obstacleGrid.size();
int n=obstacleGrid[0].size();
vector<vector<int>> res;
//初始节点有障碍或者目标节点有障碍
if(obstacleGrid[0][0]==1||obstacleGrid[m-1][n-1]==1)
return res;
//dp[i][j]表示是否可以到达(i,j)
vector<vector<bool>> dp(m,vector<bool>(n,false));
dp[0][0]=true;
//初始化首行
for(int i=1;i<n;i++)
//(0,i)可以到达的条件是(0,i-1)可以到达且(0,i)无障碍物
dp[0][i]=(obstacleGrid[0][i]==0)&&dp[0][i-1];
//初始化首列
for(int i=1;i<m;i++)
//(i,0)可以到达的条件是(i-1,0)可以到达且(i,0)无障碍物
dp[i][0]=(obstacleGrid[i][0]==0)&&dp[i-1][0];
//计算
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
{
//(i,j)可达的条件是(i,j)无障碍物并且(i-1,j)或者(i,j-1)可达
dp[i][j]=(obstacleGrid[i][j]==0)&&(dp[i-1][j]||dp[i][j-1]);
}
}
//判断终点是否可达
if(!dp[m-1][n-1])
return res;
//可达则倒序计算
int a=m-1,b=n-1;
while(a>0||b>0)
{
//逆序加入坐标
res.push_back({a,b});
//上一步是上方可达的情况
if(a>0&&dp[a-1][b])
a--;
//上一步是左方可达的情况
else
b--;
}
//插入起点
res.push_back({0,0});
//反转数组
reverse(res.begin(),res.end());
return res;
}
总结:注意分析思路,从初始节点到目标节点。如果是递归,则是从当前节点推导到当前节点的右节点或者当前节点的下节点,即判断从当前节点到目标节点是否存在一条无障碍路径,当将当前节点加入路径后,还需判断从当前节点的右节点或者从当前节点的下节点到目标节点是否存在一条无障碍路径;如果是动态规划,则是当前节点是由当前的左节点或者当前的上节点推导而来,即先判断所有节点是否可达,如果终点可达再去逆序加入路径数组。