63. 不同路径 II
1.动态规划
-
这类求方案数的问题,都有一个特点:全局的方案数,取决于子问题的方案数,就能往动态规划的方向上考虑。
-
状态转移方式与62. 不同路径 类似,某个节点的方案数只要考虑上方与左方的方案数即可,只是这里多了一个障碍物的判断,如果某点是障碍物,那么到达此点的可行方案就是0。加上障碍物判断后,此题的规划方式和上题一样。
class Solution { public: int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) { int m=obstacleGrid.size(),n=obstacleGrid[0].size(); vector<vector<int>> dp(m,vector<int>(n)); dp[0][0]=obstacleGrid[0][0]==0?1:0; for(int i=1;i<n;i++){ if(obstacleGrid[0][i]==1){ dp[0][i]=0; }else{ dp[0][i]=dp[0][i-1]; } } for(int i=1;i<m;i++){ if(obstacleGrid[i][0]==1){ dp[i][0]=0; }else{ dp[i][0]=dp[i-1][0]; } } for(int i=1;i<m;i++){ for(int j=1;j<n;j++){ if(obstacleGrid[i][j]==1){ dp[i][j]=0; }else{ dp[i][j]=dp[i-1][j]+dp[i][j-1]; } } } return dp[m-1][n-1]; } };
2.滚动数组优化
-
不难发现,上面普通的动态规划方案需要的空间复杂度是 O ( m n ) O(mn) O(mn),我们可以用滚动数组思想来优化这个复杂度。
滚动数组,说白了就是重复使用数组,减小空间浪费。可以发现,对于 d p [ i ] [ j ] dp[i][j] dp[i][j],对他结果产生影响的只有 d p [ i ] [ j − 1 ] 、 d p [ i − 1 ] [ j ] dp[i][j-1]、dp[i-1][j] dp[i][j−1]、dp[i−1][j],并且我们规划的顺序是一行一行的完成的。那我们是否能只用一个长度为n的一维数组 d p [ n ] dp[n] dp[n]来完成?
-
假设我们当前规划到了第i行,第j列, d p [ j ] dp[j] dp[j]中代表的数字正是原来二维数组中 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]的数值, d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1]的数值正是 d p [ j − 1 ] dp[j-1] dp[j−1],除了这两个数字之外,我们的状态更新再不需要别的位置的信息。因此得到的状态转移方程为:
d p [ j ] = d p [ j − 1 ] + d p [ j ] dp[j]=dp[j-1]+dp[j] dp[j]=dp[j−1]+dp[j],但如果j=0,它没有j-1这个位置的元素, d p [ j ] = d p [ j ] dp[j]=dp[j] dp[j]=dp[j],不需要更新。
-
只要在这个基础上,加上方法一中说明的障碍物判断即可完成任务。
class Solution { public: int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) { int m=obstacleGrid.size(),n=obstacleGrid[0].size(); vector<int> dp(n); dp[0]=obstacleGrid[0][0]==1?0:1; for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ if(obstacleGrid[i][j]==1){ dp[j]=0; }else if(j>=1){ dp[j]=dp[j]+dp[j-1]; } } } return dp[n-1]; } };