Python 练习 动态规划: LeetCode 64 最小路径和
给定一个包含非负整数的m x n
网格grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例 1:
输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径1→3→1→1→1
的总和最小。
示例 2:
输入:grid = [[1,2,3],[4,5,6]]
输出:12
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 200
0 <= grid[i][j] <= 200
题解
动态规划 (Dynamic Programming)
这一题应当采用动态规划的思路。我一开始没看清题目,以为节点可以向四个方向扩展,最后发现题目中规定了节点只能向两个方向扩展
之后,我想对于m×n
矩阵,从左上角走到右下角只要m+n-1
步,就想着能不能在m+n-1
步走完整个动态规划的过程,设计了下面的算法
class Solution(object):
def minPathSum(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
m = len(grid) # row num
n = len(grid[0]) # col num
pos = [0, 0]
for _ in range(m+n-2):
if pos[0] == m-1:
grid[pos[0]][pos[1]+1] += grid[pos[0]][pos[1]]
pos[1] += 1
elif pos[1] == n-1:
grid[pos[0]+1][pos[1]] += grid[pos[0]][pos[1]]
pos[0] += 1
else:
grid[pos[0]][pos[1]+1] += grid[pos[0]][pos[1]]
grid[pos[0]+1][pos[1]] += grid[pos[0]][pos[1]]
if grid[pos[0]][pos[1]+1] > grid[pos[0]+1][pos[1]]:
pos[0] += 1
else:
pos[1] += 1
return grid[-1][-1]
这个算法看上去没有什么问题,如果成立的话时间复杂度在 O(m+n),但是它违背了动态规划中更新全局信息的思路,只关注了眼前一条路径而没有考虑可能出现的更优路线。比如下面的矩阵:
1 3 1
1 5 1
4 2 1
按照上面的算法,走的路线将是左下角的L
型路线,但是实际上右上角的7
型路线更优。由于在第1
轮动态规划的过程中,grid[1][0]
处的值比grid[0][1]
的值更小,所以先选择了左下角的路线,然后由于这个算法没有更新全局信息,从而导致无法重新寻到更优的路线,一错到底
下面是LeetCode题解,算法时间复杂度为 O(mn)。需要遍历整个m×n
矩阵以保证全局信息在动态规划的过程中没有遗漏
class Solution(object):
def minPathSum(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
for i in range(len(grid)):
for j in range(len(grid[0])):
if i == 0 and j == 0:
continue
elif i == 0:
grid[i][j] += grid[i][j-1]
elif j == 0:
grid[i][j] += grid[i-1][j]
else:
grid[i][j] += min(grid[i-1][j], grid[i][j-1])
return grid[-1][-1]
动态规划算法的时空复杂度分布如下
小结:
动态规划的核心思想是将大问题分解为小问题,并保存小问题的解,以避免重复计算。这种优化技术通常用于处理递归算法中的重叠子问题,从而提高算法的效率
动态规划算法一般包含以下步骤:
定义子问题: 将原问题分解为较小的子问题。这些子问题应该具有原问题的相似性,并且它们的解可以被重复利用;
找到最优子结构: 问题的最优解可以通过最优子问题的解来构建,即问题的整体最优解可以通过子问题的最优解递归地计算得到;
建立状态转移方程: 制定子问题之间的递推关系,即状态转移方程。通过这个方程,可以从小的子问题开始递推计算,最终得到原问题的解;
确定边界条件: 确定最小的子问题的解,即边界条件。这些边界条件用于递归的终止条件,一般就是问题定义的初始状态;
自底向上或自顶向下求解: 动态规划可以通过自底向上的迭代方法或自顶向下的递归方法来求解问题;自底向上通常使用迭代循环,而自顶向下则常常利用递归实现