062 不同路径
1. 题目描述
一个机器人位于一个 m x n m x n mxn 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
提示
- 1 < = m , n < = 100 1 <= m, n <= 100 1<=m,n<=100
- 题目数据保证答案小于等于 2 ∗ 1 0 9 2 * 10^9 2∗109
2. 思路&代码
2.1 动态规划
我们用 f ( i , j ) f(i, j) f(i,j) 表示从左上角走到 ( i , j ) (i, j) (i,j) 的路径数量,其中 i i i 和 j j j 的范围分别是 [ 0 , m ) [0, m) [0,m) 和 [ 0 , n ) [0, n) [0,n)。
由于我们每一步只能从向下或者向右移动一步,因此要想走到 ( i , j ) (i, j) (i,j),如果向下走一步,那么会从 ( i − 1 , j ) (i-1, j) (i−1,j) 走过来;如果向右走一步,那么会从 ( i , j − 1 ) (i, j-1) (i,j−1) 走过来。因此我们可以写出动态规划转移方程:
f ( i , j ) = f ( i − 1 , j ) + f ( i , j − 1 ) f(i, j) = f(i-1, j) + f(i, j-1) f(i,j)=f(i−1,j)+f(i,j−1)
需要注意的是,如果 i = 0 i=0 i=0,那么 f ( i − 1 , j ) f(i-1,j) f(i−1,j) 并不是一个满足要求的状态,我们需要忽略这一项;同理,如果 j = 0 j=0 j=0,那么 f ( i , j − 1 ) f(i,j-1) f(i,j−1) 并不是一个满足要求的状态,我们需要忽略这一项。
初始条件为 f ( 0 , 0 ) = 1 f(0,0)=1 f(0,0)=1,即从左上角走到左上角有一种方法。
最终的答案即为 f ( m − 1 , n − 1 ) f(m-1,n-1) f(m−1,n−1)。
细节
为了方便代码编写,我们可以将所有的 f ( 0 , j ) f(0, j) f(0,j) 以及 f ( i , 0 ) f(i, 0) f(i,0) 都设置为边界条件,它们的值均为 1 1 1。
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
f = [[1] * n] + [[1] + [0] * (n - 1) for _ in range(m - 1)]
print(f)
for i in range(1, m):
for j in range(1, n):
f[i][j] = f[i - 1][j] + f[i][j - 1]
return f[m - 1][n - 1]
2.2 组合数学
思路与算法
从左上角到右下角的过程中,我们需要移动 m + n − 2 m+n-2 m+n−2 次,其中有 m − 1 m-1 m−1 次向下移动, n − 1 n-1 n−1 次向右移动。因此路径的总数,就等于从 m + n − 2 m+n-2 m+n−2 次移动中选择 m − 1 m-1 m−1 次向下移动的方案数,即组合数:
C m + n − 2 m − 1 = ( m + n − 2 m − 1 ) = ( m + n − 2 ) ( m + n − 3 ) ⋯ n ( m − 1 ) ! = ( m + n − 2 ) ! ( m − 1 ) ! ( n − 1 ) ! {\Large C}_{m+n-2}^{m-1} = \binom{m+n-2}{m-1} = \frac{(m+n-2)(m+n-3)\cdots n}{(m-1)!} = \frac{(m+n-2)!}{(m-1)!(n-1)!} Cm+n−2m−1=(m−1m+n−2)=(m−1)!(m+n−2)(m+n−3)⋯n=(m−1)!(n−1)!(m+n−2)!
因此我们直接计算出这个组合数即可。计算的方法有很多种:
如果使用的语言有组合数计算的 A P I API API,我们可以调用 A P I API API 计算;
如果没有相应的 A P I API API,我们可以使用 ( m + n − 2 ) ( m + n − 3 ) ⋯ n ( m − 1 ) ! \frac{(m+n-2)(m+n-3)\cdots n}{(m-1)!} (m−1)!(m+n−2)(m+n−3)⋯n 进行计算。
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
return comb(m + n - 2, n - 1)
070 爬楼梯
1. 题目描述
2. 思路&代码
参考:Orust
2.1 直接递归解法
# 直接递归解法,容易超时,python可以加个缓存装饰器,这样也算是将递归转换成迭代的形式了
# 除了这种方式,还有增加步长来递归,变相的减少了重复计算
# 还有一种方法,在递归的同时,用数组记忆之前得到的结果,也是减少重复计算
class Solution:
@functools.lru_cache(100) # 缓存装饰器
def climbStairs(self, n: int) -> int:
if n == 1: return 1
if n == 2: return 2
return self.climbStairs(n-1) + self.climbStairs(n-2)
2.2 直接DP,空间复杂度O(n)
# 直接DP,新建一个字典或者数组来存储以前的变量,空间复杂度O(n)
class Solution:
def climbStairs(self, n: int) -> int:
dp = {}
dp[1] = 1
dp[2] = 2
for i in range(3,n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
2.3 还是DP,空间复杂度O(1)
# 还是DP,只不过是只存储前两个元素,减少了空间,空间复杂度O(1)
class Solution:
def climbStairs(self, n: int) -> int:
if n==1 or n==2: return n
a, b, temp = 1, 2, 0
for i in range(3,n+1):
temp = a + b
a = b
b = temp
return temp
2.4 斐波那契数列的计算公式
# 直接斐波那契数列的计算公式喽
class Solution:
def climbStairs(self, n: int) -> int:
import math
sqrt5=5**0.5
fibin=math.pow((1+sqrt5)/2,n+1)-math.pow((1-sqrt5)/2,n+1)
return int(fibin/sqrt5)
2.5 面向测试用例编程
# 面向测试用例编程
class Solution:
def climbStairs(self, n: int) -> int:
a = [1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040,1346269,2178309,3524578,5702887,9227465,14930352,24157817,39088169,63245986,102334155,165580141,267914296,433494437,701408733,1134903170,1836311903]
return a[n-1]
078 子集
1. 题目描述
2. 思路&代码
2.1 库函数
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
for i in range(len(nums)+1):
for tmp in itertools.combinations(nums, i):
res.append(tmp)
return res
2.3 迭代
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = [[]]
for i in nums:
res = res + [[i] + num for num in res]
return res
2.3 递归(回溯算法)
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
res = []
n = len(nums)
def helper(i, tmp):
res.append(tmp)
for j in range(i, n):
helper(j + 1,tmp + [nums[j]] )
helper(0, [])
return res