动态规划
记忆化搜索,把已经计算过的内容存下来,不需要重复计算。(斐波那契数列)自上而下。
memo = [-1] * (n + 1)
def f2(n):
if n == 0:
return 0
if n == 1:
return 1
if memo[n] == -1:
memo[n] = f2(n - 1) + f2(n - 2)
return memo[n]
动态规划:将原问题拆解成若干子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案。斐波那契数列是自上而下思考问题的方式,先考虑f(5),然后到f(4)…f(0),而动态规划是自下而上的思考方式,先考虑f(1),f(2)…然后f(5)。
def f3(n):
memo = [-1] * (n + 1)
for i in range(0,n + 1):
if i == 0:
memo[i] = 0
elif i == 1:
memo[i] = 1
else:
memo[i] = memo[i - 1] + memo[i - 2]
return memo[n]
动态规划常常适用于有重叠子问题和最优子结构性质的问题,所以很多时候动态规划问题也是一个递归问题。
状态:递归函数的定义
状态转移方程:根据当前的状态,推断出下一个状态的方程。
通常把它转换为一个二维数组的问题。
问题:
有个小偷偷东西,他的背包容量是C,物品的重量和价值对应,请问小偷应该怎么偷东西才能获得最大价值的东西。
状态:定义一个状态p(i,w),该状态表示加入第i个物品,在背包容量为w的时候的解,并且我们把最优解定义为m(i,w)
状态转移方程:
当加入一个新的物品i,在容量为w的情况下,它无非有两种可能性:
1.当前的物品可以加入进来m(i,w)=m(i-1,w-wi)+Vi
2.不把当前物品加入进来:m(i,w)=m(i-1,w)
状态转移方程:
横轴是背包容量,纵轴是物体(每个物体只有一个,每一次拿一个物品放进背包)
右下角就是最终值。
题目:
给定一个包含正数的非空数组,是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
找到一些正整数,每个数只能用一次,使得这些数的和等于整个数组元素的和的一半。背包的容量就是数组和的一半,每个数就是要添进背包里的值。
状态:
定义dp[i][j]为[0,i]个物品中挑选若干个,能否使得这些数字和等于j,其中i表示当前步骤拿进来的数字,j也就是正整数之和
状态转移方程:
当前数字比正整数之和的一半大,则保持同一列上一次的状态。nums[i] > j:dp[i][j] = dp[i - 1][j]
当前数字比正整数之和的一半小的时候,就要看加上上一个数满不满足条件,用[j - nums[i]]找到上一个数的状态,依次类推就把大问题转化为小问题了。
nums[i] < = j :dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]
def canPartition(self,nums):
nums_sum = sum(nums)
if nums_sum % 2 !=0:
return False
c = nums_sum // 2
dp = [[0]*(c + 1) for _ in range(lem(nums))]
#对第一列数据进行处理全为1,方便后面j - num时该数字刚好是数组和的一半
for i in range(len(nums)):
dp[i][0] = 1
#对放进来的第一个数进行处理,因为它满足条件
if nums[0] <= c:
dp[0][nums[0]] = 1
#第一个for循环控制列
for i in range(1,len(nums)):
num = nums[i]
#第二个for循环控制行
for j in range(1,c + 1):
if num > j:
dp[i][j] = dp[i - 1][j]
continue
dp[i][j] = dp[i - 1][j] or dp[i - 1][j - num]
return dp[-1][-1]
题目:
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
因为指数增长的速度是最快的,但是3 * 3比2 * 2 * 2大,3 * 1 比2 * 2 要小,所以我们只需要找尽可能多的3相乘,然后剩下的用2相乘就行了,但是最后不能是3 * 1。
class Solution:
def cutRope(self, number):
# write code here
if number == 1:
return 1
if number == 2:
return 2
if number == 3:
return 3
three = number // 3
if three * 3 + 1 == number:
three -= 1
two = (number - three * 3) // 2
return pow(3,three)*pow(2,two)
最长公共子序列
题目:
给定两个字符串text1和text2,返回这两个字符串的最长公共子序列(子序列中字母的相对位置不能发生改变)
状态:定义dp[i][j],表示s1的前i个字符和s2的前j个字符最长公共子序列的大小。
状态转移方程:
1.当S1[i] == S2[j]:dp[i][j] = dp[i-1][j-1] + 1,当遇到两个字符串中的字符相同的时候,取上一个两个字符串都没有加入的时候的状态加1,因为这时候的状态就是没有引入新的字母的时候的最大公共子序列。
此时的右下角最后一个的位置,就是由e行d列的这个数(不加入e时的最大子序列)加1得到。
2.当S1[i] != S2[j]:dp[i][j] = max(dp[i - 1][j]),dp[i][j-1])
在纵向如果找到相同的会加1,在上面的一行找到了也会加1,所以不同的时候要去它相对位置上面和左边的最大值。
def solution(text1,text2):
#防止最后一个越界了算不到。所以直接加一位
dp = [[0] * (len*(text2) + 1) for _ in range(len(text1) + 1)]
for i in range(1,len(text1) + 1):
for j in range(1,len(text1) + 1):
if text1[i - 1] == text2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j],dp[i][j - 1])
return dp[-1][-1]