动态规划
三大算法:分治法、动态规划与贪婪算法
分治法与动态规划的区别:
分治法将大问题分成小问题,例如二分法。子问题属性不变,小问题之间互相独立,需要做一个合并的过程(从上到下的方法)
动态规划也是将大问题拆解成小问题,不同之处是他的小问题有很多重复的,我们不需要重复的做,只需要将这些小问题存储起来,便于之后的查询,这种现象叫做记忆法!(从下到上的方法:先解决这些小问题)
斐波那契数列就是典型的动态规划
要做DP的问题需要给出一个公式,这个很重要
一维动态规划
1、给定n,找到不同的将n写成1,3,4相加的方法有多少个,顺序不一样算一种
n = 5,输出6
n = 1+1+1+1+1=1+3+4=1+3+1=3+1+1=1+4=4+1
解析:与斐波那契数一样,f(n) = f(n-1) + f(n-3) + f(n-4)
f(0) = f(1) = f(2) = 1 f(3) = 2 f(4) = 4
def count(n):
dp = [None] * (n+1)
dp[0] = dp[1] = dp[2] = 1
dp[3] = 2
for i in range(4,n+1):
dp[i] = dp[i-1] + dp[i-3] + dp[i-4]
return dp[5]
2、找到不相邻的加和最大数
假设你是一个职业抢劫犯,你打算洗劫一个街道,每一个房子中都有一定数量的钱,限制你的唯一条件是相邻的房子的安保系统是相连的,如果你抢劫相邻房子那么保安系统就会报警
给定一个非负整数的列表代表每个房子中的钱,计算在不惊动警察的情况下你可以抢劫到的最多的钱
def robe(array):
n = len(array)
# 生成两行n+1的0,第一列都为0,代表初始值
# 规定第二行不取当前值所达到的最大值,第一行取当前值所达到的最大值
dp = [[0 for _ in range(n + 1)] for _ in range(2)]
for i in range(1,n + 1):
dp[0][i] = dp[1][i-1] + array[i-1]
dp[1][i] = max(dp[0][i-1], dp[1][i-1])
return max(dp[0][n], dp[1][n])
array = [2,7,9,3,1]
robe(array)
> 12
这里是引入了两个空间,因此时间复杂度为N,空间复杂度也为N.但其实我们每次只用到了当前值的前面的那两个数字,因此我们可以做出优化,不用引入空间。
def robe(array):
n = len(array)
# 初始值
yes, no = 0, 0
for i in range(n):
# no = max(yes, no) 不对,他们两个应该是同时变化
# yes = no + array[i]
yes, no = no + array[i], max(yes, no)
return max(yes, no)
变形,现在这些银行排成一个圆环该如何做
如果是一个圆环的话,我们可以再上述的情况下,加一下思考:圆环我们肯定是要选择一个起点和终点的,只不过选择了起点就不能选择终点了,f(n)分成两种情况:1、选择起点,剩下的n-1,就和非变形的一样了,f(1, n-1); 2、不选择起点,那么可以选择第二个元素f(2, n-1)
def rob_round(array):
def robe(array):
n = len(array)
# 初始值
yes, no = 0, 0
for i in range(n):
yes, no = no + array[i], max(yes, no)
return max(yes, no)
return max(robe(array[:-1]), robe(array[1:]))
def rob(nums):
if len(nums) == 0:
return 0
if len(nums) == 1:
return nums[0]
return max(robRange(nums, 0, len(nums) - 1),\
robRange(nums, 1, len(nums)))
def robRange(nums, start, end):
yes, no = nums[start], 0
for i in range(start + 1, end):
no, yes = max(no, yes), i + no
return max(no, yes)