1. 程式人生 > >Leetcode: Unique Path I & II, Minimum Path Sum, Triangle

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的試試?