动态规划专题 (I) :Leetcode 62 不同路径 + Leetcode 63 不同路径II + Leetcode 64 最小路径和
写在前面:
很多人看到动规会很害怕,我之前也是。若是理解了动规是一个怎么样的过程,其实做起来也不是很难。
动规,在我看来实际上是分治的一种想法。可以见底下Leetcode 62题,他将一个大问题,分解为小问题,并从若干个个小问题中取最优的那个结果。而这个大问题分解成小问题的过程,被称之为:转移状态方程。
完成整个动规需要考虑两个因素:
- 如何确定边界点上的值(这一般直接取决于题意)
- 如何确定状态转移方程,这取决于题目内部的逻辑,是比较重要的一块。
Leetcode 62 不同路径
题目描述
一个机器人位于一个 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
第一反应:divide and conquer递归:
很容易可以看出,当前 网格的结果数等于 个网格数下的结果,加上 个网格数下的结果之和。然后讨论一下 , 的时候就可以了:
代码如下:
def uniquePaths_divide_and_conquer(m, n):
def iterate(m, n):
if (m == 1) | (n == 1):
return 1
if m == 2:
return n
if n == 2:
return m
if (m > 2) & (n > 2):
return iterate(m-1, n) iterate(m, n-1)
return iterate(m, n)
特别简单,不知道为什么这道题说是中等难度。
改进
考虑到不断递归调用,有很多冗余的计算,那我考虑用一个矩阵来存储这些点:
def uniquePaths(m, n):
if (m == 1) | (n == 1):
return 1
if (m == 2) | (n == 2):
return max(m, n)
# 初始化
matrix = [None] * m
for i in range(0, m):
matrix[i] = [None] * n
matrix[0][0] = 0
for i in range(1, m):
matrix[i][0] = 1
for i in range(1, n):
matrix[0][i] = 1
# 开始递归实现:
for i in range(1, m):
for j in range(1, n):
matrix[i][j] = matrix[i-1][j] matrix[i][j-1]
return matrix[-1][-1]
比如一个 的网格,他的矩阵长这样:
一个非常好的性质,就是从数值为
这个点到数值为
的点一共有
种不同的走法。(例如在一个
的网格,一共有
种不同的走法)
因此最后输出位于右下角的元素即可。
提交结果
执行用时 :16 ms, 在所有 Python 提交中击败了88.64% 的用户
内存消耗 :11.7 MB, 在所有 Python 提交中击败了82.39%的用户
Leetcode 63 不同路径II
与上面一题不同的是,这题里面有障碍物,障碍物所在的地方是不能走的,那稍微复杂了一点点,只需要每步讨论的时候,考虑是否上方或左方以及自己本身是否有障碍物就可以了:
递推公式:
网站上有一个比较坑的例子导致第一次没过,就是起始点是有障碍物的情况。所以其实一开始直接讨论如果地图 有障碍物直接输出 就可以了。
代码如下:
def uniquePathsWithObstacles(obstacleGrid):
m = len(obstacleGrid)
n = len(obstacleGrid[0])
if obstacleGrid[0][0] == 1:
return 0
# 初始化
matrix = [None] * m
for i in range(0, m):
matrix[i] = [None] * n
for i in range(0, m):
for j in range(0, n):
if i == 0:
if j == 0:
matrix[i][j] = 1
else:
if (obstacleGrid[i][j] == 1) | (obstacleGrid[i][j-1] == 1):
matrix[i][j] = 0
else:
matrix[i][j] = matrix[i][j-1]
elif (j == 0) & (i != 0):
if (obstacleGrid[i-1][j] == 1) | (obstacleGrid[i][j] == 1):
matrix[i][j] = 0
else:
matrix[i][j] = matrix[i-1][j]
else:
if (obstacleGrid[i][j] == 1) | (obstacleGrid[i-1][j] obstacleGrid[i][j-1] == 2):
matrix[i][j] = 0
else:
if obstacleGrid[i][j-1] == 1:
matrix[i][j] = matrix[i-1][j]
elif obstacleGrid[i-1][j] == 1:
matrix[i][j] = matrix[i][j-1]
else:
matrix[i][j] = matrix[i][j-1] matrix[i-1][j]
return matrix[-1][-1]
如果有这样一个地图:
那么显然只有两种可能的路径:(红色与蓝色)
那么我们运行下来的递推矩阵是这样的:
跟我们预期的输出结果相同,因此这个算法是可行的。
提交结果:
执行时间:32ms
内存消耗:11.8MB
Leetcode 64 最小路径和
相似思路还有一道题:
题目描述:
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[ [1,3,1],
[1,5,1],
[4,2,1]]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
因为目标点都在边界上,则我们只需要求到前两个点所需的最短距离就可以了。整个递推式如下所示:
这样取矩阵右下角,就是最短的路径:
代码如下:
def minPathSum(grid):
m = len(grid)
n = len(grid[0])
# 初始化
matrix = [None] * m
for i in range(0, m):
matrix[i] = [None] * n
for i in range(0, m):
for j in range(0, n):
if i == 0:
if j == 0:
matrix[i][j] = grid[i][j]
else:
matrix[i][j] = matrix[i][j-1] grid[i][j]
elif j == 0:
matrix[i][j] = matrix[i-1][j] grid[i][j]
else:
matrix[i][j] = min(matrix[i-1][j], matrix[i][j-1]) grid[i][j]
return matrix[-1][-1]
提交结果:
执行时间:75 ms
内存消耗:12.9 MB