记不住过去的人,注定要重蹈覆辙。
对于一个模型为n的问题,将其分解为k个规模较小的子问题(阶段),按顺序求解子问题,前一子问题的解,为后一子问题提供有用的信息。在求解任一子问题时,通过决策求得局部最优解,依次解决各子问题。最后通过简单的判断,得到原问题的解。
斐波那契数列又称黄金分割数列。因数学家莱昂纳多-斐波那契以兔子繁殖为例引入,故又称兔子数列。
1, 1, 2, 3, 5, 8, 13, 21...
在数学上满足递推的方法定义:
F(0) = 0
F(1) = 1
F(n) = F(n-1) + F(n-2) (n>=2)
def fib(n):
if n <= 0:
return 0
if n == 1:
return 1
return fib(n-1) + fib(n-2)
分析:
上图中的二叉树的每个子节点都需要执行一次,如果n= 6,则需要再向下延申,fib(2)就需要执行5次。每次调用时都需要保留上下文,在时间和空间上开销很大。那如果我们把每次计算的结果保存起来,下次用到的时候直接通过查表得方式调用,就可以节省大量得时间,这就是动态规划得基本思想。
def fib_dp(n):
#定义一个dp数组,记录每个n的值,这里n+1长度的便于写代码
dp = [-1] * (n+1)
#初始化
dp[1] = dp[2] = 1
for i in range(3, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
核心思想是递推,难点在于dp[i]状态代表什么,然后构造转移矩阵,利用初始条件递推出最终结果。
解题步骤:
动态规划的要素:问题的最优解由相关子问题的最优解组合而成,并且可以独立求解子问题(最优子结构)。
https://leetcode.cn/problems/unique-paths/description/
思路:
每一步只能从向下或向右移动一步,所以对于坐标(i,j)要么从(i-1,j)过来(向下走一步),要么从(i,j-1)过来(向右走一步)。
状态定义:
dp(i, j)表示从左上角走到(i,j)的路径数量
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
#定义dp数组,用dp(i,j)表示从左上角走到(i,j)的路径数量dp(i,j)
dp = [[0] * n for _ in range(m)]
# 初始化dp数组,第一行和第一列、应该都是1,都只有一种情况,从左边或者上边过来
dp[0][0] = 1
for i in range(m):
dp[i][0] = 1
for j in range(n):
dp[0][j] = 1
#print(dp) # 可以看下dp [[1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0]]
#计算剩余位置,填充好dp数组
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
print(dp) # [[1, 1, 1, 1, 1, 1, 1], [1, 2, 3, 4, 5, 6, 7], [1, 3, 6, 10, 15, 21, 28]]
## 通过查表的方式,返回最终结果
return dp[m-1][n-1]
https://leetcode.cn/problems/0i0mDW/description/
思路:
与上一题类似,对于坐标(i,j)要么从(i-1,j)过来(向下走一步),要么从(i,j-1)过来(向右走一步),但是加了条件,每个坐标上的值有了意义,需要进行累加处理。
状态:
设dp为大小m*n的矩阵,其中dp(i,j)的值代表直到走到(i,j)的最小路径和。
转移方程:
dp(i, j) = grid(i, j) + min(dp(i-1, j), dp(i, j-1))
dp(i, j) = grid(i, j ) + dp(i, j-1)
dp(i, j) = grid(i, j) + dp(i, j-1)
dp(i, j) = grid(i, j)
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
rows = len(grid)
cols = len(grid[0])
#其中dp(i, j)的值代表直到走到(i,j)的最小路径和
dp = [[0] * cols for _ in range(rows)]
for i in range(rows):
for j in range(cols):
#起点
if i == 0 and j == 0:
dp[i][j] = grid[i][j]
# 中间的点,可以从左边和上边过来
elif i != 0 and j != 0:
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
#只能从左边过来
elif i == 0 and j != 0:
dp[i][j] = grid[i][j] + dp[i][j-1]
#只能从上边过来
elif i != 0 and j == 0:
dp[i][j] = grid[i][j] + dp[i-1][j]
#print(dp) # grid =[[1,3,1],[1,5,1],[4,2,1]]
return dp[rows-1][cols-1]
https://leetcode.cn/problems/egg-drop-with-2-eggs-and-n-floors/description/
思路:
开始有两枚鸡蛋,所以要分情况讨论,还剩两枚鸡蛋,和一枚鸡蛋;
状态:
dp(i, j)表示有 i+1 鸡蛋时,验证 j 层楼需要的最少操作次数,我们可以分开分析 i = 0 和 i = 1的情况:
·i = 0 时(只有一枚鸡蛋了):
需要逐层检验,当在 j 层楼时,则dp(0, j) = j
i = 1时:
(1)假设当前在k层的时候第一枚鸡蛋碎了,那么问题就转换成了dp(0, k-1),总共的操作次数是,dp(0, k-1) + 1;
(2)如果当前在k层丢下鸡蛋,没有碎,此时可以证明在k层的时候鸡蛋不会碎,那么问题就转化成dp(1, j-k),总共的操作次数是dp(1, j-k) + 1
基于(1)(2)取最坏情况:
max(dp(0, k-1), dp(1, j-k) + 1)
综上,
dp(1, j) = min(dp(1, j), max(dp(0, k-1), dp(1, j-k) + 1))
转移方程:
dp(0, j) = j, i=0
dp(1, j) = min(dp(1, j), max(dp(1, j), max(dp(0, k-1), dp(1, j-k) + 1))), i=1
class Solution:
def twoEggDrop(self, n: int) -> int:
# dp(i, j)表示有i+1枚鸡蛋时,验证j层楼需要的最少操作次数
dp = [[sys.maxsize] * (n + 1) for _ in range(2)]
#初始化dp数组
dp[0][0] = dp[1][0] = 0
#初始化,只有一枚鸡蛋的情况
for j in range(n+1):
dp[0][j] = j
for j in range(n+1):
#两枚鸡蛋时,在k层是否碎了,分情况讨论
for k in range(j + 1):
dp[1][j] = min(dp[1][j], max(dp[0][k-1] + 1, dp[1][j-k] + 1))
# 查表返回最终结果
return dp[1][n]
数据结构与算法(python)http://t.csdnimg.cn/Gb6MN
程序 = 数据结构 + 算法;而且在面试过程中这些是必考,必问的内容。内容大纲:基础数据结构(树、链表、栈、队列等)、常见算法(排序算法、递归算法等)。
专栏是基于python的基础知识,是很好的入门学习资料。帮助大家快速理解这些数据结构和常见算法的概念,同时结合力扣题目,也能更好的掌握这些知识,达到在面试中游刃有余的效果。
python基础精讲 http://t.csdnimg.cn/HdKdi
本专栏主要针对python基础语法,帮助学习者快速接触并掌握python大部分最重要的语法特征。
1、基本数据类型和变量
2、分支结构与循环结构
3、函数与异常处理
4、类与模块
5、文件读写
通过本专栏可以快速掌握python的基础语法。