1、描述
一个机器人位于一个m x n网格的左上角(起始点在下图中标记为“Start”)。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
请问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
说明:m和n的值不超过100。
例1:输入:m = 3, n = 2
输出:3
解释:从左上角开始,总共有3条路径可以到达右下角。
1)右下->向右->向下
2)向右->向下->向右
3)向下->向右->向右
例2:输入:m = 7, n = 3
输出:28
2、算法
1)递归
思想:
求 ( 0 , 0 ) 点到( m - 1 , n - 1) 点的走法。
(0,0)点到(m - 1 , n - 1) 点的走法等于(0,0)点右边的点 (1,0)到(m - 1 , n - 1)的走法加上(0,0)点下边的点(0,1)到(m - 1 , n - 1)的走法。
而左边的点(1,0)点到(m - 1 , n - 1) 点的走法等于(2,0) 点到(m - 1 , n - 1)的走法加上(1,1)点到(m - 1 , n - 1)的走法。
下边的点(0,1)点到(m - 1 , n - 1) 点的走法等于(1,1)点到(m - 1 , n - 1)的走法加上(0,2)点到(m - 1 , n - 1)的走法。
然后一直递归下去,直到 (m - 1 , n - 1) 点到(m - 1 , n - 1) ,返回 1。
当我们求点 (x,y)到(m - 1 , n - 1) 点的走法的时候,递归求了点 (x,y)点右边的点 (x + 1,0)到(m - 1 , n - 1)的走法和(x,y)下边的点(x,y + 1)到(m - 1 , n - 1)的走法。而没有考虑到(x + 1,0)到(m - 1 , n - 1)的走法和点(x,y + 1)到(m - 1 , n - 1)的走法是否是之前已经求过了。事实上,很多点求的时候后边的的点已经求过了,所以再进行递归是没有必要的。基于此,我们可以用 visited 保存已经求过的点。
func uniquePaths(_ m: Int, _ n: Int) -> Int {
var visited : [String : Int] = [String : Int]()
return getAns(0, 0, m-1, n-1, 0, visited)
}
private func getAns(_ x : Int, _ y : Int, _ m : Int, _ n : Int,_ num : Int, _ visited : [String : Int])->Int{
var visited = visited
if x == m && y == n {
return 1
}
var n1 = 0
var n2 = 0
var key : String = String(x+1)+"@"+String(y)
//判断当前点是否已经求过了
if visited[key] == nil {
if x+1 <= m {
n1 = getAns(x+1, y, m, n, num, visited)
}
}else{
n1 = visited[key]!
}
key = String(x)+"@"+String(y+1)
if visited[key] == nil {
if y+1 <= n {
n2 = getAns(x, y+1, m, n, num, visited)
}
}else{
n2 = visited[key]!
}
//将当前点加入visited中
key = String(x)+"@"+String(y)
visited[key] = n1+n2
return n1+n2
}
2)公式
思想:
func uniquePaths6(_ m: Int, _ n: Int) -> Int {
//初始化最后一列
let N = n+m-2
let k = m-1
var res = 1
var i = 1
while i <= k{
res = res * (N-k+i) / i
i += 1
}
return res
}
3)动态规划
解法一
思路:
我们令 dp[i][j] 是到达 i, j 最多路径
动态方程:dp[i][j] = dp[i-1][j] + dp[i][j-1]
注意,对于第一行 dp[0][j],或者第一列 dp[i][0],由于都是在边界,所以只能为 1
时间复杂度:O(m*n)
空间复杂度:O(m*n)
优化:因为我们每次只需要 dp[i-1][j],dp[i][j-1]
所以我们只要记录这两个数
/*
时间复杂度:O(m*n)
空间复杂度:O(m*n)
*/
func uniquePaths(_ m: Int, _ n: Int) -> Int {
var dp : [[Int]] = [[Int]].init(repeating: [Int].init(repeating: 0, count: n), count: m)
for i in 0..<n {
dp[0][i] = 1
}
for i in 0..<m {
dp[i][0] = 1
}
for i in 1..<m {
for j in 1..<n {
dp[i][j] = dp[i-1][j] + dp[i][j-1]
}
}
return dp[m-1][n-1]
}
//优化空间复杂度O(2n)
func uniquePaths(_ m: Int, _ n: Int) -> Int {
var pre : [Int] = [Int].init(repeating: 1, count: n)
var cur : [Int] = [Int].init(repeating: 1, count: n)
for i in 1..<m {
for j in 1..<n {
cur[j] = cur[j-1]+pre[j]
}
pre = cur
}
return pre[n-1]
}
//优化空间复杂度O(n)
func uniquePaths(_ m: Int, _ n: Int) -> Int {
var cur : [Int] = [Int].init(repeating: 1, count: n)
for i in 1..<m {
for j in 1..<n {
cur[j] += cur[j-1]
}
}
return cur[n-1]
}
解法二
思想:基于递归的基础上优化算法,要做的就是要省略压栈的过程,直接出栈。很明显可以做到的,只需要初始化最后一列为 1 ,然后 1 列,1 列的向前更新就可以了。有一些动态规划的思想了。
func uniquePaths(_ m: Int, _ n: Int) -> Int {
//初始化最后一列
var dp : [Int] = [Int].init(repeating: 1, count: m)
//从右向左更新所有列
var i = n-2
while i >= 0 {
//最后一行永远是1,所以从倒数第2行开始
//从下往上更新所有行
var j = m-2
while j >= 0{
dp[j] = dp[j] + dp[j+1]
j -= 1
}
i -= 1
}
return dp[0]
}
解法三
思想:就是从左向右,从上到下一行一行更新(当前也可以一列一列更新)
时间复杂度:O(m*n)
func uniquePaths(_ m: Int, _ n: Int) -> Int {
//初始化最后一列
var dp : [Int] = [Int].init(repeating: 1, count: n)
for i in 1..<m {
for j in 1..<n {
dp[j] = dp[j]+dp[j-1]
}
}
return dp[n-1]
}