题目:
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1 和 0 来表示。
1. 实现算法
总的来说还是比较清晰,
1. 确定dp数组(dp table)以及下标的含义
2. 确定递推公式
3. dp数组如何初始化
4. 确定遍历顺序
5. 举例推导dp数组
首先确定这个dp是一个二维数组,因为要考虑障碍,这些障碍压缩不到一行。
然后,确定与dp数组的每个格子代表行至此格的方法数,每个格子都是与真实网格obstacleGrid相对应的。
这一题的主要trick就是第一行第一列的障碍物后面必须是0。也就是说,只要遇到了一个障碍物,那在这之后的格子对应的每个路径都是0。在我的代码里,尝试过很多丑陋的写法,最终呈现如下方代码中的这一段所示:
if (obstacleGrid[0][0] == 1)
return 0;
dp[0][0] = 1;
for(int i = 1; i < width; ++i)
{
if(obstacleGrid[0][i] != 1 && dp[0][i-1] != 0)
dp[0][i] = 1;
}
for(int j = 1; j < lenth; ++j)
{
if(obstacleGrid[j][0] != 1 && dp[j-1][0] != 0)
dp[j][0] = 1;
}
在上面这段代码中,我依靠的是判断前一个格子的状态。
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid)
{
// 记录网格的长和宽
int lenth = obstacleGrid.size();
int width = obstacleGrid[0].size();
// 新建与真实网格obstacleGrid对应的dp数组,每个格子代表行至此格的方法数
int dp[lenth][width];
memset(dp,0,sizeof(dp));
//vector<vector<int>> dp(lenth, vector<int>(width, 0));
if (obstacleGrid[0][0] == 1)
return 0;
dp[0][0] = 1;
for(int i = 1; i < width; ++i)
{
if(obstacleGrid[0][i] != 1 && dp[0][i-1] != 0)
dp[0][i] = 1;
}
for(int j = 1; j < lenth; ++j)
{
if(obstacleGrid[j][0] != 1 && dp[j-1][0] != 0)
dp[j][0] = 1;
}
for(int i = 1; i < lenth; ++i)
{
for(int j = 1; j < width; ++j)
{
if(obstacleGrid[i][j] == 1)
dp[i][j] = 0;
else
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[lenth-1][width-1];
}
};
2. 优化
后来我看了一个解法,其他过程差不多,初始化第一行第一列那边就写了一点点:
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++)
dp[i][0] = 1;
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++)
dp[0][j] = 1;
刚开始看的时候觉得障碍物后面还是没考虑到,然后发现遇到障碍物,for循环直接停止了。
(注)3. int dp[][]和vector描述二维数组的区别
经常看到解答里面使用vector表示二维数组。
int dp[lenth][width];
和vector<vector<int>> dp(m, vector<int>(n, 0));
的区别在于它们使用的底层数据结构不同。
第一个声明 int dp[lenth][width]; 声明了一个二维数组,使用的是 C++ 中的原生数组。这个数组的大小是在编译时就固定了的,无法动态改变大小。这也意味着当数组过大时,可能会导致堆栈溢出等问题。
第二个声明 vector<vector> dp(m, vector(n, 0)); 声明了一个二维向量,使用的是 C++ STL 中的 vector 容器。这个容器可以动态调整大小,并且可以在运行时根据需要分配更多的内存空间。使用 vector 容器可以避免 C++ 原生数组所遇到的问题,而且还提供了许多方便的方法和操作符,如 push_back、emplace_back、size、at 等。
因此,一般情况下,使用 vector 容器来代替原生数组是更为方便和安全的选择。