leetcode 63. 不同路径 II

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode 63. 不同路径 II相关的知识,希望对你有一定的参考价值。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述



记忆化递归

  • 如果是没有障碍物的话,从(0,0)出发,每次只能往下走、或者往右走。假设i表示行、j表示列,当前点为(i,j),那么每次只能往(i + 1, j)移动、或者往(i, j + 1)移动。

在这里插入图片描述

  • 对于有障碍物的情况下怎么办呢?
    其实也简单,直接返回0就可以了,这表能走到障碍物的路径总和为0。

在这里插入图片描述

  • 有了上述条件,递归就很容易写了,每次往右、或者往下走。
  • 到达边界条件返回0,到达终点返回1。
    在这里插入图片描述
  • 上图中橙色的(2,2)这个点,表示从这里发出到达终点有多少路径。
  • 同理,(0,0)就是从起点出发,走到终点有多少路径,也就是题目的要求。

注意,纯递归是不行的,因为有大量的重复计算,需要加个缓存。

class Solution {
	map<pair<int,int>, int> map;//用来保存计算结果
public:
	int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) 
	{
		if (obstacleGrid[0][0] == 1)//起点有障碍物,那么直接返回0
			return 0;
		if (obstacleGrid[obstacleGrid.size() - 1][obstacleGrid[0].size() - 1] == 1)//终点存在障碍物
			return 0;
		return dfs(obstacleGrid, 0, 0);
	}
	int dfs(vector<vector<int>>& obstacleGrid, int i, int j)
	{
		//如果到达当前点的走法总数已经算出,那么直接返回
		if (map.find({ i,j }) != map.end())
			return map[{i, j}];
		//越界检查和障碍物检查
		if (i >= obstacleGrid.size() || j >= obstacleGrid[0].size() || obstacleGrid[i][j] == 1)
			return 0;
		//到达左下角,返回1
		if (i == obstacleGrid.size() - 1 && j == obstacleGrid[0].size() - 1)
			return 1;
		//到达当前点的走法
        	map[{i, j}] =dfs(obstacleGrid, i+1, j) + dfs(obstacleGrid, i, j+1);
		return map[{i, j}];
	}
};

在这里插入图片描述


动态规划—起点到终点

  • 对于动态规划,我们可以反过来想。 如何到达下图中橙色的(1,2)这个点。
    在这里插入图片描述
  • 只能由两个方向而来,上方、或者是左方;对于(3,2)障碍物这个点来说,能到达这里的路径就是0。
    所以对于(i,j)这个点来说,其动态规划转移方程就是:
if 当前点不是障碍物:
    dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
else:
    dp[i][j] = 0
  • 我们还需要处理下边界情况,也就是第一列、第一行时
    在这里插入图片描述
  • 如上图,只要第一列中的某个格子是障碍物,那么这个格子跟后面的都无法到达。
  • 同理,第一行中如果有格子是障碍物,那么这个格子跟后面的都无法到达了。
  • 时间复杂度:O(M * N)
  • 空间复杂度:O(M * N)
class Solution {
public:
	int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid)
	{
		if (obstacleGrid[0][0] == 1) return 0;
		int r = obstacleGrid.size();
		int c = obstacleGrid[0].size();
		if (obstacleGrid[r - 1][c - 1] == 1) return 0;
		vector<vector<int>> dp(r, vector<int>(c));
		dp[0][0] = 1;
		//处理第一列
		for (int i = 1; i < r; ++i)
		{
			//当前格子有障碍或者当前格子所处列有障碍物
			if (obstacleGrid[i][0] == 1 || dp[i - 1][0] == 0)
			{
				dp[i][0] = 0;
			}
			else
			{
				dp[i][0] = 1;
			}
		}
		//处理第一行
		for (int j = 1; j < c; j++)
		{
			if (obstacleGrid[0][j] == 1 || dp[0][j - 1] == 0)
			{
				dp[0][j] = 0;
			}
			else
			{
				dp[0][j] = 1;
			}
		}
		for (int i = 1; i < r; i++)
		{
			for (int j = 1; j < c; j++)
			{
				//当前格子存在障碍物
				if (obstacleGrid[i][j] == 1)
					dp[i][j] = 0;
				else//路径总数来自于上方(dp[i-1][j])和左方(dp[i][j-1])   
					dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
			}
		}
		return dp[r - 1][c - 1];
	}
};

在这里插入图片描述


动态规划—终点到起点

  • 跟【动态规划-1】解法正好是反过来的,【动态规划-1】解法中,是从(0,0)走到(n-1,m-1)。
  • 【动态规划-2】中,是从(n-1,m-1)走到(0,0)。
  • 所以我们反过来推导,对于(2,2)这个点,只能从下方、右方转移过来。
    在这里插入图片描述
    同样,反着推的时候也需要处理下边界问题,也就是最后一行,最后一列需要单独处理一下。这里的思路跟前一种解法是一样的,只是倒退来的。

在这里插入图片描述
时间复杂度:O(M * N)
空间复杂度:O(M * N)

class Solution {
public:
	int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid)
	{
		if (obstacleGrid[0][0] == 1) return 0;
		int r = obstacleGrid.size();
		int c = obstacleGrid[0].size();
		if (obstacleGrid[r - 1][c - 1] == 1) return 0;
		vector<vector<long>> dp(r, vector<long>(c));
		//最小子结果
		dp[r - 1][c - 1] = 1;
		//处理第一列
		for (int i = r - 2; i >= 0; --i)
		{
			//当前格子有障碍或者当前格子所处列有障碍物
			if (obstacleGrid[i][c - 1] == 1 || dp[i+1][c - 1] == 0)
			{
				dp[i][c - 1] = 0;
			}
			else
			{
				dp[i][c - 1] = 1;
			}
		}
		//处理第一行
		for (int j = c - 2; j >= 0; --j)
		{
			if (obstacleGrid[r - 1][j] == 1 || dp[r - 1][j+1] == 0)
			{
				dp[r - 1][j] = 0;
			}
			else
			{
				dp[r - 1][j] = 1;
			}
		}
		for (int i = r - 2; i >= 0; i--)
		{
			for (int j = c - 2; j >= 0; j--)
			{
				//当前格子存在障碍物
				if (obstacleGrid[i][j] == 1)
					dp[i][j] = 0;
				else//路径总数来自于上方(dp[i-1][j])和左方(dp[i][j-1])   
					dp[i][j] = dp[i + 1][j] + dp[i][j + 1];
			}
		}
		return dp[0][0];
	}
};

在这里插入图片描述


动态规划+空间优化

  • 对于动态规划的两种解法,都是只需要上一层的解,而不需要上上一层的。`
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
  • 也就是求第i行时,只需要i-1行已求解过的值,不需要i-2行的了。
  • 所以这里可以用滚动数组进行优化,将二维数组改为一维数组。
  • 一维数组的大小为列的长度。
    在这里插入图片描述
  • 第三次迭代时,求第三个格子6时,由于左边的值已经是已知的,第二次迭代时同位置的值也是已知的。所以当前值的计算方式就是:
  • 这里是把地图一行一行的看,一行一行的求解,求解当前行只要确保上一行已经求出来即可
  • 因此这里的一位数组的大小就是列的长度,相当于每求解新的一行,是从新的一行的第一个元素开始求解。
  • 但注意这里还用到了滚动数组的思想,即当前列对应上一行的该列元素就是当前列还未被替换的元素
  • 因为用到了滚动数组,那么这里的最小子问题就成了第一行第一列的第一个元素
计算当前值 = 以求出的左边值 + 上一次迭代同位置的值
dp[j] = dp[j - 1] + dp[j]

时间复杂度:O(M * N)
空间复杂度:O(M)

class Solution {
public:
	int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid)
	{
		if (obstacleGrid[0][0] == 1) return 0;
		int r = obstacleGrid.size();
		int c = obstacleGrid[0].size();
		if (obstacleGrid[r - 1][c - 1] == 1) return 0;
		vector<int> dp(c);
		//这里滚动数组的第一个元素,即每一行的第一个元素,只要当前格子没有障碍物,就都是1
		dp[0] = 1;
		for (int i = 0; i<r; i++)
		{
			for (int j = 0; j <c; j++)
			{
				//有障碍物的格子直接赋0
				if (obstacleGrid[i][j] == 1)
					dp[j] = 0;
				//否则dp[j]的值由左方和上一次迭代的dp[j]累加而来
				else if (obstacleGrid[i][j] == 0 && j - 1 >=0)
					dp[j] = dp[j] + dp[j - 1];
			}
		}
		return dp[c-1];
	}
};

在这里插入图片描述

以上是关于leetcode 63. 不同路径 II的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 63. 不同路径 II

leetcode 每日一题 63. 不同路径 II

leetcode 每日一题 63. 不同路径 II

LeetCode 63 不同路径II

LeetCode-63. 不同路径 II

Leetcode之动态规划(DP)专题-63. 不同路径 II(Unique Paths II)