算法强化 —— 动态规划

动态规划

1.斐波那契数列
2.记忆化搜索
3.动态规划



def f1(n):
    if n == 0:
        return 0
    if n == 1:
        return 1
    return f1(n-1)+f1(n-2)

记忆化搜索

我们已经知道哥哥的递归计算方式的问题是重复计算,那么我们把已经计算过的内容存下来就好了,这种方法叫做记忆化搜索



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]

动态规划(DP)

将原问题拆解成若干个子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案

刚刚的两种方式都是一种自上而下思考问题的方式,比如在斐波那契数列里面,先考虑f5,然后考虑f4,…,f0,而动态规划一般来说是使用一种自下而上的思考方式
所以按照这种方式动态规划的计算逻辑
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]

对比

记忆化搜索和动态规划最大的不同是思考方式的不同,前者是自上而下,后者是自下而上

0-1背包问题

有个小偷偷东西,他的背包容量是C,物品的重量weight和价值对应value,请问小偷应该怎么偷东西才能获得价值最大的东西
C = 11

item value weight
1 1 1
2 6 2
3 18 5
4 22 6
5 28 7

状态和状态转移方程

动态规划常常适用于有重叠子问题和最优子结构性质的问题,所以很多时候 动态规划问题也是一个递归问题
状态:递归函数的定义
状态转移方程:根据当前的状态,推断出下一状态的方程

0-1背包问题

状态:定义一个状态p(i,w),该状态表示加入第i个物品,背包容量为w的时候的解,并且我们把最优解定义为m(i,w)
状态转移方程
当加入一个新物品i,在容量为w的情况下,它无非有两种可能性
1.当前的物品可以加入进来 m ( i , w ) = m ( i 1 , w ) + v i m(i,w) = m(i-1,w)+v_i
2.不把当前物品加入进来:m(i,w) = m(i-1,w)
最终可以得到的状态转移方程为

m ( i , w ) = { 0  if  i = 0 0  if  w = 0 m ( i 1 , w )  if  w i > w max { m ( i 1 , w ) , m ( i 1 , w w i ) + v i }  otherwise  m(i, w)=\left\{\begin{array}{cc} 0 & \text { if } i=0 \\ 0 & \text { if } w=0 \\ m(i-1, w) & \text { if } w_{i}>w \\ \max \left\{m(i-1, w), m\left(i-1, w-w_{i}\right)+v_{i}\right\} & \text { otherwise } \end{array}\right.

leetcode 416 分隔等和子集

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:
每个数组中的元素不会超过100
数组的大小不会超过200

扫描二维码关注公众号,回复: 10988876 查看本文章

示例1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成[1, 5, 5]和[11].

示例2:

输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.

给定一个只包含整数的非空数组
是否可以从这个数组中挑选出一些正整数,每个数只能用一次,使得这些数的和等于整个数组元素的和的一半。

定义状态和状态转移方程
状态:定义dp[i][j]为[0,i]个物品中挑选若干个,能否使得这些数求和等于j
其中i表示当前步骤拿进来物品的item,j表示背包的容量,也就是正整数之和
状态转移方程
当nums[i]>j:dp[i][j] = dp[i-1][j]
当nums[i]<= j :dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i]]

class Solution:
    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(len(nums))]
        # 对第一列数据进行处理
        for i in range(len(nums)):
            dp[i][0] = 1
        # 对放进来的第一个数进行处理
        if nums[0]<=c:
            dp[0][nums[0]] = 1
        for i in range(1,len(nums)):
            num = nums[i]
            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]

    # 优化一
    # 将二维数组 只变成2行
    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(2)]
        # 对第一列数据进行处理
        dp[0][0] ,dp[1][0] = 1,1
        # 对放进来的第一个数进行处理
        if nums[0]<=c:
            dp[0][nums[0]] = 1
        for i in range(1,len(nums)):
            num = nums[i]
            for j in range(1,c+1):
                cur = i%2
                pre = 1 if cur == 0 else 0
                if num > j:
                    dp[cur][j] =  dp[pre][j]
                    continue
                dp[cur][j] = dp[pre][j] or dp[pre][j-num]

        return dp[-1][-1]

# 优化二
    def canPartition(self,nums):
        nums_sum = sum(nums)
        if nums_sum %2 != 0:
            return False
        # 和的一半作为背包容量的大小
        c = nums_sum // 2
        dp = [0]*(c+1)
        # 对第一列数据进行处理
        dp[0] = 1
        # 对放进来的第一个数进行处理
        if nums[0]<=c:
            dp[nums[0]] = 1
        for i in range(1,len(nums)):
            num = nums[i]
            for j in range(c,0,-1):
                if num>j:
                    continue

                dp[j] = dp[j] or dp[j-num]

        return dp[-1]


    # 最终优化
    def canPartition(self,nums):
        c = sum(nums)
        if c%2 !=0:
            return False
        c = c//2
        dp = [0]*(c+1)
        dp[0] =1
        for num in nums:
            for i in range(c,num-1,-1):
                dp[i] = dp[i] or dp[i-num]

        return dp[-1]

发布了110 篇原创文章 · 获赞 3 · 访问量 4097

猜你喜欢

转载自blog.csdn.net/qq_33357094/article/details/104966746