62. 不同路径
动态规划:
对于任意位置(i, j)
,到达这个位置的路径数量是到达(i-1, j)
和(i, j-1)
的路径数量之和,因为只能从上方或左侧移动到达当前位置。这种结构允许我们通过组合子问题的解来解决整个问题。计算到达某一点的路径数量需要重复计算到达该点之前所有点的路径数量。使用动态规划存储这些中间结果可以避免重复计算,从而提高效率。因此这是典型的动态规划题。
状态dp[i][j]
定义为从左上角(0, 0)
到达位置(i, j)
的唯一路径数量。这个状态是基于问题描述直接定义的。到达(i, j)
的路径数量是从它的上方(i-1, j)
和左侧(i, j-1)
到达的路径数量之和。所有位于矩阵第一行dp[0][j]
和第一列dp[i][0]
的位置都被初始化为1
,因为从起点到达这些位置只存在一条路径,要么一直向右,要么一直向下。数组是按行从上到下遍历的,每一行内部从左到右遍历。这种顺序确保在计算dp[i][j]
时,dp[i-1][j]
和dp[i][j-1]
都已经被计算过,保证了状态转移的正确性。
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [[1] * n for _ in range(m)]
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]
滚动数组优化:
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
dp = [1] * n # 初始化dp数组,长度为n,每个元素值为1
for i in range(1, m): # 从第二行开始遍历,因为第一行已经初始化
for j in range(1, n): # 从第二列开始遍历,第一列的值始终为1,不需要更新
dp[j] += dp[j - 1] # 更新dp[j],dp[j]的新值是它自己(代表上方的路径数)加上它左边的值(代表左方的路径数)
return dp[-1] # 返回最后一个元素,即到达右下角的路径数量
数论解法:
要从起点走到终点,总共需要走m−1步向下和n−1步向右,总步数为m−1+n−1。问题变成了,在这m+n−2步中,选择m−1步(或n−1步)来向下(或向右)走的方案数。
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
if m < n:
m, n = n, m
result = 1
for i in range(1, n):
result = result * (m + i - 1) // i
return result
63. 不同路径 II
初始化dp:遇到石头停止初始化,石头的右边和下边不可达到。
递推式:如果当前位置是石头,置为0;如果不是石头,由左边和上边求和得到。
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m, n = len(obstacleGrid), len(obstacleGrid[0])
dp = [[0] * n for _ in range(m)]
for i in range(m):
if obstacleGrid[i][0] == 1:
break
else:
dp[i][0] = 1
for j in range(n):
if obstacleGrid[0][j] == 1:
break
else:
dp[0][j] = 1
for i in range(1, m):
for j in range(1, n):
if obstacleGrid[i][j] == 0:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[m - 1][n - 1]
滚动数组优化:
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m, n = len(obstacleGrid), len(obstacleGrid[0])
dp = [0] * n
dp[0] = 1 if obstacleGrid[0][0] == 0 else 0
for i in range(m):
for j in range(n):
if obstacleGrid[i][j] == 1:
dp[j] = 0
elif j > 0:
dp[j] += dp[j - 1]
return dp[n - 1]
今日总结:
两个题还是比较直观的,滚动数组优化还需要进一步学习。