这四个题都是比较直观的DP题,题目中已经暗示了我们怎么Cache递归表达式算出来的数据,我们可以采取和题目中一样的矩阵,或者节省一部分空间采用滚动数组解题
Problem 1: Unique Path I
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?
Above is a 3 x 7 grid. How many possible unique paths are there?
分析:
这个机器人只能往右或者往下走,那么对于一个给定点坐标dp[i][j],它的来源只有两个方向,dp[i-1][j]和dp[i][j-1],我们只需要把这两个坐标里的值加起来即可。唯一要注意的是小心边界上的条件,对于第一行来说,机器人只能通过往右走到达,所以第一行每一个格子只可能有一种路径。同理对于第一列来说,机器人只能通过往下走到达,所以第一列里每一个格子也只有一种路径
public class Solution {
public int uniquePaths(int m, int n) {
if (m==0 || n==0) return 0;
int[][] dp = new int[m][n];
for(int i=0;i<m;i++){
dp[i][0] = 1;
for(int j=1;j<n;j++){
if (i==0) dp[i][j] = 1;
else dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
}
有没有节省空间的方法呢?有的,观察上面程序可以发现循环是从上到下扫描行的,在每一行里,我们又是从左到右来扫描的,我们完全可以用一个滚动数组来表示每一行,算下一行的时候,只需要更新这个数组里的值便可以了。
public class Solution {
public int uniquePaths(int m, int n) {
if (m==0 || n==0) return 0;
int[] dp = new int[n];
dp[0] = 1;
for(int i=0;i<m;i++){
for(int j=1;j<n;j++){
if (i==0) dp[j] = 1;
else dp[j] = dp[j] + dp[j-1];
}
}
return dp[n-1];
}
}
注意程序里的这一句 dp[j] = dp[j] + dp[j-1] 当我们复写这一层的dp[j]之前,dp[j]还是上一行的值,dp[j-1]是左边格子我们复写后的值,所以加起来等价于二维数组里面 dp[i][j] = dp[i-1][j] + dp[i][j-1]
Problem 2: Unique Path II
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.
[
[0,0,0],
[0,1,0],
[0,0,0]
]
The total number of unique paths is 2
.
Note: m and n will be at most 100.
分析:这一道题在上一题的基础上,多了障碍物,当我们碰到障碍物的时候,我们无论怎么走也不可能走到这个格子里,所以在这个格子里,累加的路径值为0
public class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
if (m==0||n==0) return 0;
if (obstacleGrid[0][0] == 1) return 0;
int[][] dp = new int[m][n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if (obstacleGrid[i][j]==0){
if (i==0 && j==0) dp[i][j] = 1;
else dp[i][j] = (i>0 ? dp[i-1][j] : 0) + (j>0 ? dp[i][j-1] : 0);
}
}
}
return dp[m-1][n-1];
}
}
为了简化,我们只需计算obstacleGrid矩阵里,值为0的格子就可以了,其余的格子便是障碍物,会被dp矩阵初始化为0
注意这一句
dp[i][j] = (i>0 ? dp[i-1][j] : 0) + (j>0 ? dp[i][j-1] : 0);
其实等价于下面这三句,为了偷懒我把它打成一句了
i = 0 dp[i][j] = dp[i][j-1];
j = 0 dp[i][j] = dp[i-1][j];
i >0 j>0 dp[i][j] = dp[i][j-1] + dp[i-1][j]
用滚动数组的解法:
public class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
if (m==0||n==0) return 0;
if (obstacleGrid[0][0] == 1) return 0;
int[] dp = new int[n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if (obstacleGrid[i][j]==0){
if (i==0 && j==0) dp[j] = 1;
else dp[j] = (i>0 ? dp[j] : 0) + (j>0 ? dp[j-1] : 0);
}else dp[j] = 0; //手动给障碍物格子赋值为0
}
}
return dp[n-1];
}
}
唯一注意的一点,在矩阵里,我们不用管obstacleGrid里面为1的障碍点,是因为矩阵里默认初始化值为0,但是在滚动数组里我们要复用之前数组里的值,所以我们要处理每一个矩阵中的格子,我们不能跳过障碍物的格子了,所以要手动给它们赋值为0.
Problem 3 Minimum Path Sum
Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.
Note: You can only move either down or right at any point in time.
分析:
同样的思路,刚才我们是求累加的值,现在我们只不过求最小的值,对于dp[i][j]的两个来源,我们只要取最小值再加上这一点的值便可以了。需要注意一点,如果你也用我刚才偷懒的语句写:
dp[i][j] = grid[i][j] + Math.min((i>0?dp[i-1][j]:Integer.MAX_VALUE),(j>0?dp[i][j-1]:Integer.MAX_VALUE));
这一句,要把之前的0,换成Integer.MAX_VALUE
public class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
if (m==0||n==0) return 0;
int[][] dp = new int[m][n];
for(int i=0;i<m;i++)
for(int j=0;j<n;j++){
if (i==0 && j==0) dp[0][0] = grid[0][0];
else dp[i][j] = grid[i][j] + Math.min((i>0?dp[i-1][j]:Integer.MAX_VALUE),(j>0?dp[i][j-1]:Integer.MAX_VALUE));
}
return dp[m-1][n-1];
}
}
用滚动数组的解:
public class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
if (m==0||n==0) return 0;
int[] dp = new int[n];
for(int i=0;i<m;i++)
for(int j=0;j<n;j++){
if (i==0 && j==0) dp[0] = grid[0][0];
else dp[j] = grid[i][j] + Math.min((i>0?dp[j]:Integer.MAX_VALUE),(j>0?dp[j-1]:Integer.MAX_VALUE));
}
return dp[n-1];
}
}
Problem 4: Triangle
Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.
For example, given the following triangle
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
The minimum path sum from top to bottom is 11
(i.e., 2 + 3 + 5 + 1 = 11).
Note:
Bonus point if you are able to do this using only O(n) extra space, where n is the total number of rows in the triangle.
分析:
这道题看上去和矩阵的形式不同,但是我们换个写法就能发现玄机了
首先可以观察到,边界和之前的题不同了,之前的题,边界是 i = 0 , j = 0 而在这道题里边界变为了i = 0 , i = j
接下来我们看对于非边界上的点来怎么推算,图里显示的很清楚,来自上方的格子,和左上角的格子
即: 对于dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
对于左边边界i=0,只有上方一个来源 dp[i][j] = dp[i-1][j]
对于右边边界j = i,只有斜上角一个来源 dp[i][j] = dp[i-1][j-1]
还有一点需要注意的是,在上面的题目中,我们最后只要求得最右下角的值就行了,而对于Triangle,我们求的是最小和路径,路径可以通过最后一行里任意一个元素出去,所以在算出最后一行值后,要求出其中的最小值
public class Solution {
public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {
if (triangle==null) return 0;
int n = triangle.size();
int[][] dp = new int[n][n];
for(int i=0;i<n;i++)
for(int j=0;j<i+1;j++)
if(i==0 && j==0) dp[0][0] = triangle.get(0).get(0);
else dp[i][j] = triangle.get(i).get(j) + Math.min((i!=j?dp[i-1][j]:Integer.MAX_VALUE),(j>0?dp[i-1][j-1]:Integer.MAX_VALUE));
Arrays.sort(dp[n-1]);
return dp[n-1][0];
}
}