Leetcode动态规划两题
问题一
https://leetcode.com/problems/unique-paths/description/
A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
How many possible unique paths are there?
问题:
一个机器人位于一个 m x n 网格的左上角 (起始方位如图已标出)
这个机器人每次只能向下或向右移动一格。机器人试图移动到网格右下方的角落 (结束位置如图已标出)
机器人有多少种可能的移动路径?
这个问题的题解很容易用递归描述。如图很容易看出,当 m x n 网格中 m = 1 或是 n = 1时,机器人只能有一种走法 (不断向下or向右移动)。当 m、n均大于1时,可以分解成两个子问题(m - 1) x n 和 m x (n - 1),可能的移动路径数量为两个子问题解之和。Java描述如下:
public class Solution {
public int uniquePaths(int m, int n) {
if (m == 1 || n == 1) return 1;
return uniquePaths(m - 1, n) + uniquePaths(m, n - 1);
}
}
看起来真是非常的easy。但是这个代码是无法AC的,原因是超时。分析一下这一算法的时间复杂度,会发现其复杂度居然是呈指数增长!这时候我们就可以使用 动态规划(Dynamic Programming)来优化算法。
我们的递归算法慢的主要原因在于进行了过多的重复计算。我们会发现 uniquePaths(3, 3) = uniquePaths(2, 3) + uniquePaths(3, 2) = uniquePaths(1, 3) + uniquePaths(2, 2) + uniquePaths(3, 1) + uniquePaths(2, 2)
这一过程中出现了对uniquePaths(2, 2)的重复计算。
动态规划将子问题的答案系统地记录在了一个表里,避免了这样的重复计算。
如下所示,我们用一个m x n的二维数组中记录了子问题的解,如果搜索到子问题已解决,那么直接返回二维数组中记录的值。这样就在原有算法的基础上避免了重复计算。
public class Solution {
private int[][] map;
public int uniquePaths(int m, int n) {
map = new int[m][n];
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
map[i][j] = -1;
return upath(m, n);
}
public int upath(int m, int n) {
if (m == 1 || n == 1) return 1;
if (map[m-1][n-1] != -1) return map[m-1][n-1];
int res = upath(m - 1, n) + upath(m, n -1);
map[m-1][n-1] = res;
return res;
}
}
大部分时候我们会写成非递归的形式(教科书上好像都是像下面这样写的)
public class Solution {
public int uniquePaths(int m, int n) {
int[][] map = new int[m][n];
for(int i = 0; i<m;i++)
map[i][0] = 1;
for(int j= 0;j<n;j++)
map[0][j]=1;
for(int i = 1;i<m;i++)
for(int j = 1;j<n;j++)
map[i][j] = map[i-1][j]+map[i][j-1];
return map[m-1][n-1];
}
}
问题二
Follow up for “Unique Paths”:
Now consider if some obstacles are added to the grids. How many unique paths would there be?
An obstacle and empty space is marked as 1 and 0 respectively in the grid.
For example, There is one obstacle in the middle of a 3x3 grid as illustrated below.
继续上一道题。
现在考虑有的网格中存在障碍物,那么在这种情况下还有多少种走法?
有障碍物和无障碍物的方格分别被标记为1和0。如下图即为一个障碍物在正中的3 x 3网格。
[
[0,0,0],
[0,1,0],
[0,0,0]
]
这道题本质上和上一道题是一样的,同样使用动态规划的方法解决。不过在此基础上需要稍作改变。
上一道题中我们将递归的基本条件,1 x m和 n x 1网格的走法置为1。但是在这里,我们将障碍物和障碍物之前的走法置为0。对于存在障碍物的网格[0, 0, 1, 1, 0],我们认为每个方格对应的走法数量为[0, 0, 0, 0, 1]。而对于其它存在障碍物的方格,我们统一认为该方格对应的走法为0。代码在第一题的基础上稍作修改后如下。
public class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] map = new int[m][n];
for(int i = 0, k = 1; i<m;i++) {
if (obstacleGrid[i][0] == 1) k = 0;
map[i][0] = k;
}
for(int j= 0, k = 1;j<n;j++) {
if (obstacleGrid[0][j] == 1) k = 0;
map[0][j]=k;
}
for(int i = 1;i<m;i++)
for(int j = 1;j<n;j++) {
if (obstacleGrid[i][j] == 1) map[i][j] = 0;
else map[i][j] = map[i-1][j]+map[i][j-1];
}
return map[m-1][n-1];
}
}