【程序员面试金典】面试题 08.02. 迷路的机器人

文章介绍了如何设计算法解决机器人从网格左上角到右下角的路径问题,考虑机器人只能向下或向右移动且遇到障碍物1时不能通过。提供了两种解题思路:递归搜索和动态规划。在递归方法中,通过DFS判断是否存在无障碍路径;在动态规划方法中,建立二维DP数组判断每个位置是否可达,然后逆序构造路径。
摘要由CSDN通过智能技术生成

【程序员面试金典】面试题 08.02. 迷路的机器人

题目描述

描述:设想有个机器人坐在一个网格的左上角,网格 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;
}

总结:注意分析思路,从初始节点到目标节点。如果是递归,则是从当前节点推导到当前节点的右节点或者当前节点的下节点,即判断从当前节点到目标节点是否存在一条无障碍路径,当将当前节点加入路径后,还需判断从当前节点的右节点或者从当前节点的下节点到目标节点是否存在一条无障碍路径;如果是动态规划,则是当前节点是由当前的左节点或者当前的上节点推导而来,即先判断所有节点是否可达,如果终点可达再去逆序加入路径数组。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值