LeetCode 62 不同路径
不同路径算是二维dp的简单题了, 根据题意可以知道到达每个网格的不同路径可以有两个来源,递推关系就是累加这两个方向的不同路径数量,就能得到到达当前网格的不同路径总数:
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
if m == 1 and n == 1:
return 1
# dp数组定义及初始化
dp = [[0 for _ in range(n)] for _ in range(m)]
for i in range(m):
dp[i][0] = 1
for j in range(n):
dp[0][j] = 1
# 遍历:从上到下,从左到右
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
dp数组的含义是到达dp[i][j]的不同路径总数,可以看到,无论是前一天的一维dp还是今天的二维dp,一般来说dp数组和含义直接和问题的目标解相关。
常规二维dp的时间和空间复杂度都是O(mn),由于递推公式中dp数组的结果计算只用到了前一行的结果,用滚动数组可以优化空间复杂度为O(n):
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
if m == 1 and n == 1:
return 1
# 滚动数组
dp = [1] * n
for i in range(1, m):
for j in range(1, n):
dp[j] += dp[j-1]
return dp[-1]
讲解中提到的数论方法比较讲究思维能力和数学功底,就不强行掌握了。
LeetCode 63 不同路径II
题目链接:63. 不同路径 II - 力扣(Leetcode)
不同路径II中在原不同路径的基础上增加了障碍物的限制条件,其实解题方法是完全一样的,有障碍物的地方路径数量置零即可。二维dp数组全部元素初始化为0,第一行第一列的初始化要考虑障碍物的因素,出现障碍物之后的网格都不可达,也就不需要继续初始化为1了,直接break:
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m, n = len(obstacleGrid), len(obstacleGrid[0])
# if m == 1 and n == 1:
# return 1
# if obstacleGrid[m-1][n-1]:
# return 0
# dp数组定义及初始化
dp = [[0 for _ in range(n)] for _ in range(m)]
for i in range(m):
if obstacleGrid[i][0]:
break # 有障碍及之后的位置均不可达
dp[i][0] = 1
for j in range(n):
if obstacleGrid[0][j]:
break
dp[0][j] = 1
for i in range(1, m):
for j in range(1, n):
if obstacleGrid[i][j]:
continue # 有障碍的位置路径数为0
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
上述代码中,m==n==1表示起点即终点,直接可以返回1;如果终点直接存在障碍物,终点不可达,也可以直接返回0,不用继续遍历了。但这些特殊条件也可以不额外列出,遍历之后的结果也是一样的,时间和空间复杂度与不同路径的分析相同,不再赘述。