Leetcode: Unique Path I & II, Minimum Path Sum, Triangle
這四個題都是比較直觀的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];
}
}
好了,和矩陣有關的DP基礎就是這些,自己試著把最後一題Triangle改寫成O(n) space的試試?