leetcode 64. 最小路径和 动态规划系列
1. leetcode 64. 最小路径和
给定一个包含非负整数的 m x n 网格,
请找出一条从左上角到右下角的路径,
使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
1.1. 暴力
cost(i,j)=grid[i][j]+min(cost(i+1,j),cost(i,j+1))
def minPathSum_1(self, grid):
"""
暴力法
时间复杂度 :O(2^(m+n})。每次移动最多可以有两种选择。
空间复杂度 :O(m+n)。递归的深度是 m+n。
:param grid:
:return:
"""
len_i, len_j = len(grid), len(grid[0])
def _helper(grid, i, j, len_i, len_j):
if i == len_i or j == len_j:
return float('inf')
if i == len_i - 1 and j == len_j - 1:
return grid[i][j]
return grid[i][j] + min(_helper(grid, i+1, j, len_i, len_j),
_helper(grid, i, j+1, len_i, len_j),)
return _helper(grid, 0, 0, len_i, len_j)
1.2. 二维动态规划
新建一个额外的dp数组,与原矩阵大小相同。在这个矩阵中,dp(i, j)表示从坐标 (i, j)到右下角的最小路径权值。
初始化右下角的dp值为对应的原矩阵值,然后去填整个矩阵,对于每个元素考虑移动方式,获得最小路径;
如下递推公式:
dp(i,j)=grid(i,j)+min(dp(i+1,j),dp(i,j+1))
注意边界情况。
def minPathSum_2(self, grid):
"""
二维动态规划
时间复杂度 :O(mn)。遍历整个矩阵恰好一次。
空间复杂度 :O(n)。额外的矩阵。
:param grid:
:return:
"""
len_i, len_j = len(grid), len(grid[0])
dp = [[0 for _ in range(len_j)] for _ in range(len_i)]
for i in range(len_i - 1, -1, -1):
for j in range(len_j - 1, -1, -1):
if i == len_i - 1 and j != len_j - 1:
dp[i][j] = grid[i][j] + dp[i][j+1]
elif j == len_j -1 and i != len_i - 1:
dp[i][j] = grid[i][j] + dp[i+1][j]
elif j != len_j -1 and i != len_i - 1:
dp[i][j] = grid[i][j] + min(dp[i+1][j], dp[i][j+1])
else:
dp[i][j] = grid[i][j]
return dp[0][0]
1.2.1. 动态规划优化
在上一解法中使用数组记录了所有的路径值,但实际只需要记录一行的数据,所以可以做如下优化:
def minPathSum_3(self, grid):
"""
动态规划 一维数组
上面的动态规划保存了整个矩阵,
但实际只用保存一行/列的数据即可
时间复杂度 :O(mn)。遍历整个矩阵恰好一次。
空间复杂度 :O(n)。额外的一维数组,和一行大小相同。
:param grid:
:return:
"""
len_i, len_j = len(grid), len(grid[0])
dp = [0]*len_j for i in range(len_i - 1, -1, -1):
for j in range(len_j - 1, -1, -1):
if i == len_i - 1 and j != len_j - 1:
dp[j] = grid[i][j] + dp[j+1]
elif j == len_j - 1 and i != len_i - 1:
dp[j] = grid[i][j] + dp[j]
elif i != len_i - 1 and j != len_j - 1:
dp[j] = grid[i][j] + min(dp[j], dp[j+1])
else:
dp[j] = grid[i][j]
return dp[0]
1.2.2. 优化2
当然也可以使用数组本身的空间,使得所需的额外空间为零。
需要注意的是改变数组本身的行为不要放在其它解决方案测试之前;
否则使用copy.deepcopy()。
def minPathSum_4(self, grid: [[int]]) -> int:
"""
动态规划
不使用额外空间
时间复杂度:O(MN)
空间复杂度:O(1)
:param grid:
:return:
"""
for i in range(len(grid)):
for j in range(len(grid[0])):
if i == j == 0: continue
elif i == 0: grid[i][j] = grid[i][j - 1] + grid[i][j]
elif j == 0: grid[i][j] = grid[i - 1][j] + grid[i][j]
else: grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j]
return grid[-1][-1]
2. 完整代码及执行结果
# coding:utf-8 __author__ = "sn" """
64. 最小路径和
给定一个包含非负整数的 m x n 网格,
请找出一条从左上角到右下角的路径,
使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。 示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
""" """
思路:
暴力穷举
动态规划 """ class Solution:
def minPathSum_1(self, grid):
"""
暴力法
时间复杂度 :O(2^(m+n})。每次移动最多可以有两种选择。
空间复杂度 :O(m+n)。递归的深度是 m+n。
:param grid:
:return:
"""
len_i, len_j = len(grid), len(grid[0]) def _helper(grid, i, j, len_i, len_j):
if i == len_i or j == len_j:
return float('inf')
if i == len_i - 1 and j == len_j - 1:
return grid[i][j]
return grid[i][j] + min(_helper(grid, i+1, j, len_i, len_j),
_helper(grid, i, j+1, len_i, len_j),) return _helper(grid, 0, 0, len_i, len_j) def minPathSum_2(self, grid):
"""
二维动态规划
时间复杂度 :O(mn)。遍历整个矩阵恰好一次。
空间复杂度 :O(n)。额外的矩阵。
:param grid:
:return:
"""
len_i, len_j = len(grid), len(grid[0])
dp = [ [0] * len_j ] * len_i for i in range(len_i - 1, -1, -1):
for j in range(len_j - 1, -1, -1):
if i == len_i - 1 and j != len_j - 1:
dp[i][j] = grid[i][j] + dp[i][j+1]
elif j == len_j -1 and i != len_i - 1:
dp[i][j] = grid[i][j] + dp[i+1][j]
elif j != len_j -1 and i != len_i - 1:
dp[i][j] = grid[i][j] + min(dp[i+1][j], dp[i][j+1])
else:
dp[i][j] = grid[i][j]
return dp[0][0] def minPathSum_3(self, grid):
"""
动态规划 一维数组
上面的动态规划保存了整个矩阵,
但实际只用保存一行/列的数据即可
时间复杂度 :O(mn)。遍历整个矩阵恰好一次。
空间复杂度 :O(n)。额外的一维数组,和一行大小相同。
:param grid:
:return:
"""
len_i, len_j = len(grid), len(grid[0])
dp = [0]*len_j for i in range(len_i - 1, -1, -1):
for j in range(len_j - 1, -1, -1):
if i == len_i - 1 and j != len_j - 1:
dp[j] = grid[i][j] + dp[j+1]
elif j == len_j - 1 and i != len_i - 1:
dp[j] = grid[i][j] + dp[j]
elif i != len_i - 1 and j != len_j - 1:
dp[j] = grid[i][j] + min(dp[j], dp[j+1])
else:
dp[j] = grid[i][j]
return dp[0] def minPathSum_4(self, grid: [[int]]) -> int:
"""
动态规划
不使用额外空间
时间复杂度:O(MN)
空间复杂度:O(1)
:param grid:
:return:
"""
for i in range(len(grid)):
for j in range(len(grid[0])):
if i == j == 0: continue
elif i == 0: grid[i][j] = grid[i][j - 1] + grid[i][j]
elif j == 0: grid[i][j] = grid[i - 1][j] + grid[i][j]
else: grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j]
return grid[-1][-1] import timeit
from collections import Iterable def processing_func(cls, func_name, *ar, **kw):
func = getattr(cls, func_name) if isinstance(ar[0], Iterable):
res = func(*ar[0])
else:
res = func()
# 打印执行结果
print('执行结果:', res) import timeit
def test_func(cls, *ar): # 方法执行耗时
time_cost = dict() # 获取并执行Solution类中的解决方法
func_list = [x for x in dir(cls) if not x.startswith('__')]
print('\r\n', "共计有个方法:"%(len(func_list)), func_list)
# 设置参数 # 依次执行Solution类中的方法
for i, _ in enumerate(func_list, 1):
# 跳过
#if i == 1: continue
# 设置参数
func = getattr(cls, _)
# print(cls, func_num, func, ar) # 打印方法说明文档
print("\r\n", "*" * 40, "\r\n方法[%s]:%s\r\n说明:%s" % (i, _, func.__doc__.strip()),) # 执行方法并记录执行时长
t = timeit.timeit(stmt="processing_func(so, '{}', para)".format(_),
setup='from __main__ import processing_func, so, para',
number= 1
)
time_cost[_] = t
print('\r\n执行时长:', *time_cost.items(), sep='\r\n') if __name__ == "__main__":
# 实例化解决方案类
so = Solution() # 参数设定
li = [
[1, 3, 1],
[1, 5, 1],
[4, 2, 1],
]
para = (li,) test_func(so, para)
pass
2.1. 执行结果
共计有个方法: ['minPathSum_1', 'minPathSum_2', 'minPathSum_3', 'minPathSum_4'] ****************************************
方法[1]:minPathSum_1
说明:暴力法
时间复杂度 :O(2^(m+n})。每次移动最多可以有两种选择。
空间复杂度 :O(m+n)。递归的深度是 m+n。
:param grid:
:return:
执行结果: 7 ****************************************
方法[2]:minPathSum_2
说明:二维动态规划
时间复杂度 :O(mn)。遍历整个矩阵恰好一次。
空间复杂度 :O(n)。额外的矩阵。
:param grid:
:return:
执行结果: 7 ****************************************
方法[3]:minPathSum_3
说明:动态规划 一维数组
上面的动态规划保存了整个矩阵,
但实际只用保存一行/列的数据即可
时间复杂度 :O(mn)。遍历整个矩阵恰好一次。
空间复杂度 :O(n)。额外的一维数组,和一行大小相同。
:param grid:
:return:
执行结果: 7 ****************************************
方法[4]:minPathSum_4
说明:动态规划
不使用额外空间
时间复杂度:O(MN)
空间复杂度:O(1)
:param grid:
:return:
执行结果: 7 执行时长:
('minPathSum_1', 0.00013762672739037798)
('minPathSum_2', 3.638943978457454e-05)
('minPathSum_3', 2.332656396447088e-05)
('minPathSum_4', 2.239350140589196e-05)
leetcode 64. 最小路径和 动态规划系列的更多相关文章
- [LeetCode] 64. 最小路径和 ☆☆☆(动态规划)
描述 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. 说明:每次只能向下或者向右移动一步. 示例: 输入:[ [1,3,1], [1,5,1 ...
- LeetCode 64. 最小路径和(Minimum Path Sum) 20
64. 最小路径和 64. Minimum Path Sum 题目描述 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. 说明: 每次只能向下或 ...
- Java实现 LeetCode 64 最小路径和
64. 最小路径和 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. 说明:每次只能向下或者向右移动一步. 示例: 输入: [ [1,3,1], ...
- LeetCode 64最小路径和
题目 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. 说明:每次只能向下或者向右移动一步. 示例: 输入: [ [1,3,1], [1,5 ...
- Leetcode——64. 最小路径和
题目描述:题目链接 同样对于这个问题,我们可以考虑用动态规划来解决. 解决动态规划常见的三个步骤: 1:问题的归纳.对于 i,j 位置上的最短路径可以用d[ i ][ j ]表示. 2:归纳递推式:d ...
- leetcode 64. 最小路径和Minimum Path Sum
很典型的动态规划题目 C++解法一:空间复杂度n2 class Solution { public: int minPathSum(vector<vector<int>>&am ...
- [LeetCode]64. 最小路径和(DP)
题目 给定一个无序的整数数组,找到其中最长上升子序列的长度. 示例: 输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4 ...
- Leetcode之动态规划(DP)专题-64. 最小路径和(Minimum Path Sum)
Leetcode之动态规划(DP)专题-64. 最小路径和(Minimum Path Sum) 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. ...
- Leetcode题目64.最小路径和(动态规划-中等)
题目描述: 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小. 说明:每次只能向下或者向右移动一步. 示例: 输入: [ [1,3,1], [1, ...
随机推荐
- JavaScript DOM–事件操作
事件 注册事件 给元素添加事件,为注册事件或者绑定事件 注册事件两种方式 传统方式 监听事件方式 事件监听 addEventListener() 事件监听 (IE9以上) eventTarget.ad ...
- wxpython学习:创建最小的空的wxPython程序
frame = wx.Frame(parent=None, title=’Bare’) frame.Show() return True app = App() app.MainLoop() 上面的代 ...
- PAT 基础编程题目集 6-10 阶乘计算升级版 (20 分)
本题要求实现一个打印非负整数阶乘的函数. 函数接口定义: void Print_Factorial ( const int N ); 其中N是用户传入的参数,其值不超过1000.如果N是非负整数,则该 ...
- Mysql常见注入
Mysql显错注入 1.判断注入类型为字符型:http://219.153.49.228:43074/new_list.php?id=tingjigonggao' and 1=1 --+2.判断字段为 ...
- 使用vue/cli 创建一个简单的项目
首先,电脑安装了node.js官方要求8.9 或更高版本 (推荐 8.11.0+) npm install -g @vue/cli # OR yarn global add @vue/cli 全局安装 ...
- [CF1303F] Number of Components - 并查集,时间倒流
有一个 \(n \times m\) 矩阵,初态下全是 \(0\). 如果两个相邻元素(四连通)相等,我们就说它们是连通的,且这种关系可以传递. 有 \(q\) 次操作,每次指定一个位置 \((x_i ...
- 小白的java学习之路 “ 带参数的方法”
一.带参数的方法: 1.1 语法: <访问修饰符> 返回类型 <方法名>(<形式参数列表>) { //方法的主体 } 1.2 案例: 榨汁机 public clas ...
- 在ASP.NET 中调用 WebService 服务
一.webservice定义 详见 https://www.cnblogs.com/phoebes/p/8029464.html 二.在ASP.NET MVC 中调用 webservice 1:要调用 ...
- Scrum简介
1. 什么是Scrum Scrum是一种轻量级的框架,适合于小型的.结合紧密的团队开发复杂的产品.Scrum是二十世纪后期一些软件工程师协同努力的脑力劳动的成果,现已成为技术领域最具魅力的方法.但Sc ...
- setTimeout(call,0)作用
setTimeout(call,0)作用 经常看到setTimeout延时0ms的javascript代码,感到很迷惑,难道延时0ms和不延时不是一个道理吗?后来通过查资料以及实验得出以下两个作用, ...