1. 程式人生 > >Leetcode題解:unique-path

Leetcode題解:unique-path

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 7 x 3 grid. How many possible unique paths are there?

Note: m and n will be at most 100.

Example 1:

Input: m = 3, n = 2 Output: 3 Explanation: From the top-left corner, there are a total of 3 ways to reach the bottom-right corner: 1. Right -> Right -> Down 2. Right -> Down -> Right 3. Down -> Right -> Right

Example 2:

Input: m = 7, n = 3 Output: 28

題目給了一個n行m列的網格,一個機器人要從左上角走到右下角,並且機器人每次只能向右走或者向下走,問有多少種走法。

解法1:用組合數求解

很明顯,我們知道機器人一共有m-1次向右走,n-1次向下走,每一種走法就是n - 1個Down動作和m - 1個Right動作的排列,也就是Cn1+m1n1Cn1+m1m1C_{n - 1 + m - 1}^{n - 1}或C_{n - 1 + m - 1}^{m - 1}

因此我們可以只要計算出Cn1

+m1n1Cn1+m1m1C_{n - 1 + m - 1}^{n - 1}或C_{n - 1 + m - 1}^{m - 1}就可以得到答案,但是又有一個問題就是我們應該如何高效的計算一個組合排列數CmnC_{m }^{n}

如果我們直接按公式: Cmn=m!(mn)!n!C_m^n = \frac{m!}{(m-n)!*n!} 來計算,那麼當m和n比較大時,很容易在計算分子的時候就溢位了,並不是很通用。

既然一次性算出CmnC_{m }^{n}容易溢位,那我們可以先從更小的子問題開始。我們可以用動態規劃的思想來逐步求解出CmnC_{m }^{n}。動態規劃思想中最核心的地方就是如何劃分子問題,以及如何通過子問題得到更大的問題的解。

通過公式: Cmn=Cm1n1+Cm1nC_m^n = C_{m-1}^{n-1} + C_{m-1}^{n} 我們可以將CmnC_{m }^{n}分解為更小的兩個子問題,我們只要算出了Cm1n1Cm1nC_{m-1}^{n-1} 和 C_{m-1}^{n}的值,便可以計算出CmnC_{m }^{n}。具體實現如下:

// golang 0ms 100%
func C(m int, n int) int {
    //special cases
    if m == 0 || n == 0 || m == n {
        return 1
    }
    
    solutions := make([][]int, m + 1)
    for i := 0;i < m + 1;i++ {
        size := n + 1
        if m < n {
            size = m + 1
        }
        solutions[i] = make([]int,size)
    } 
    
    //initialize
    for i := 0;i < m + 1;i++ {
        solutions[i][0] = 1
    }
    solutions[1][1] = 1
    
    //dynamic programming
    for i := 2;i < m + 1;i++ {
        for j := 1;j < len(solutions[m]);j++ {
            solutions[i][j] = solutions[i - 1][j] + solutions[i - 1][j - 1]
            if i == m && j == n {
                break
            }
        }
    }
    return solutions[m][n]
}

func uniquePaths(m int, n int) int {
    total := m + n -2
    sel := m - 1
    if n - 1 < sel {
        sel = n - 1
    }
    return C(total, sel)
}

程式碼中函式C即是來計算組合數的公式,它通過一個m+1行n+1列的矩陣solutions來儲存計算結果,solutions[m][n]代表CmnC_{m }^{n}的值。演算法先計算出m=0和m=1時的所有組合數,之後不斷迭代直至計算出CmnC_{m }^{n}

解法2:將各個位置的路徑數作為子問題

這種解法依然是用動態規劃策略,但更加直觀。假設到達位置(i,j)的路徑數位solutions[i][j],那麼有: solutions[i][j]=solutions[i1][j]+solutions[i][j1]solutions[i][j] = solutions[i-1][j] + solutions[i][j-1]

這是因為機器人只能向右或向下移動,到達位置(i,j)必須經過(i-1,j)或(i,j-1)。演算法實現如下:

// golang 0ms 100%
func uniquePaths(m int, n int) int {
    //special cases
    if m == 1 || n == 1 {
        return 1
    }
    
    solutions := make([][]int, n)
    for i := 0;i < n;i++ {
        solutions[i] = make([]int, m)
    } 
    
    //initialize
    for i := 0;i < n;i++ {
        solutions[i][0] = 1
    }
    for j := 0;j < m;j++ {
        solutions[0][j] = 1
    }
    
    //dynamic programming
    for i := 1;i < n;i++ {
        for j := 1;j < m;j++ {
            solutions[i][j] = solutions[i - 1][j] + solutions[i][j - 1]
        }
    }
    return solutions[n - 1][m - 1]
}

第二個解法通過稍加改動,便可以來更進一步解決unique-path-ii這道題。unique-path-ii在上面這道題的基礎上,在一些位置設定了障礙物。對於有障礙物的位置(i,j),令solutions[i][j]=0即可。

// golang 4ms 100%
func uniquePathsWithObstacles(obstacleGrid [][]int) int {
    n := len(obstacleGrid)
    m := len(obstacleGrid[0])
    
    solutions := make([][]int, n)
    for i := 0;i < n;i++ {
        solutions[i] = make([]int, m)
    } 
    
    //initialize
    for i := 0;i < n;i++ {
        if obstacleGrid[i][0] == 1 {
            for j := i;j < n;j++ {
                solutions[j][0] = 0
            }  
            break 
        }
        solutions[i][0] = 1
    }
    for j := 0;j < m;j++ {
        if obstacleGrid[0][j] == 1 {
            for i := j;i < m;i++ {
                solutions[0][i] = 0
            }
            break
        }
        solutions[0][j] = 1
    }
    
    //dynamic programming
    for i := 1;i < n;i++ {
        for j := 1;j < m;j++ {
            if obstacleGrid[i][j] == 1 {
                solutions[i][j] = 0
                continue
            }
            solutions[i][j] = solutions[i - 1][j] + solutions[i][j - 1]
        }
    }
    return solutions[n - 1][m - 1]
}