Leetcode题解:unique-path

原题网址:https://leetcode.com/problems/unique-paths/

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动作的排列,也就是 C n − 1 + m − 1 n − 1 或 C n − 1 + m − 1 m − 1 C_{n - 1 + m - 1}^{n - 1}或C_{n - 1 + m - 1}^{m - 1} Cn1+m1n1Cn1+m1m1

因此我们可以只要计算出 C n − 1 + m − 1 n − 1 或 C n − 1 + m − 1 m − 1 C_{n - 1 + m - 1}^{n - 1}或C_{n - 1 + m - 1}^{m - 1} Cn1+m1n1Cn1+m1m1就可以得到答案,但是又有一个问题就是我们应该如何高效的计算一个组合排列数 C m n C_{m }^{n} Cmn

如果我们直接按公式:
C m n = m ! ( m − n ) ! ∗ n ! C_m^n = \frac{m!}{(m-n)!*n!} Cmn=(mn)!n!m!
来计算,那么当m和n比较大时,很容易在计算分子的时候就溢出了,并不是很通用。

既然一次性算出 C m n C_{m }^{n} Cmn容易溢出,那我们可以先从更小的子问题开始。我们可以用动态规划的思想来逐步求解出 C m n C_{m }^{n} Cmn。动态规划思想中最核心的地方就是如何划分子问题,以及如何通过子问题得到更大的问题的解。

通过公式:
C m n = C m − 1 n − 1 + C m − 1 n C_m^n = C_{m-1}^{n-1} + C_{m-1}^{n} Cmn=Cm1n1+Cm1n
我们可以将 C m n C_{m }^{n} Cmn分解为更小的两个子问题,我们只要算出了 C m − 1 n − 1 和 C m − 1 n C_{m-1}^{n-1} 和 C_{m-1}^{n} Cm1n1Cm1n的值,便可以计算出 C m n C_{m }^{n} Cmn。具体实现如下:

// 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]代表 C m n C_{m }^{n} Cmn的值。算法先计算出m=0和m=1时的所有组合数,之后不断迭代直至计算出 C m n C_{m }^{n} Cmn

解法2:将各个位置的路径数作为子问题

这种解法依然是用动态规划策略,但更加直观。假设到达位置(i,j)的路径数位solutions[i][j],那么有:
s o l u t i o n s [ i ] [ j ] = s o l u t i o n s [ i − 1 ] [ j ] + s o l u t i o n s [ i ] [ j − 1 ] solutions[i][j] = solutions[i-1][j] + solutions[i][j-1] solutions[i][j]=solutions[i1][j]+solutions[i][j1]

这是因为机器人只能向右或向下移动,到达位置(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]
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值