动态规划总结

2023.3.31总结

做题要领

  1. 明确dp矩阵和下标i的含义,dp[i]表示的是什么?
  2. 明确状态转移矩阵
  3. 明确dp矩阵的初始状态
  4. 明确遍历的顺序
  5. 检查方法:推到出dp矩阵的值,打印出dp矩阵的值,看看是否和设想的值一致

遍历顺序

01背包、组合

1、遍历方式讲解

        for i in range(len(nums)):
            for j in range(n,nums[i]-1,-1):

倒序遍历是为了保证物品i只被放入一次!。但如果一旦正序遍历了,那么物品0就会被重复加入多次!
举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15
如果正序遍历
dp[1] = dp[1 - weight[0]] + value[0] = 15
dp[2] = dp[2 - weight[0]] + value[0] = 30
此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。
为什么倒序遍历,就可以保证物品只放入一次呢?
倒序就是先算dp[2]
dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0)
dp[1] = dp[1 - weight[0]] + value[0] = 15
所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。

2、例题
分割等和数组
零和一

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。 示例 1:
输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        if sum(nums)%2==1:
            return False
        n=sum(nums)//2
        dp=[0 for i in range(n+1)]
        for i in range(len(nums)):
            for j in range(n,nums[i]-1,-1):
                dp[j]=max(dp[j],dp[j-nums[i]]+nums[i])
        return dp[-1]==n

根据动态规划的五部曲来分析一下:

  • 首先,分析问题类型,这是01背包问题,因为数组nums中的元素不可以重复选取,且是组合问题不是排序问题
  • dp数组的含义是,dp[j]表示背包容量是j时候能够最多装nums中物品的重量。[0, 1, 1, 1, 1, 5, 6, 6, 6, 6, 10, 11]
  • 状态转移方程:dp[j]=max(dp[j],dp[j-nums[i]]+nums[i]),数组分组题目等效于,物品质量就是物品价值的01背包问题
  • 初始化,全0即可
  • 遍历顺序,由于组合问题,物品在外层循环
完全背包、组合

1、遍历方式讲解

        for i in range(len(nums)):
            for j in range(1,n+1):
            	if j>=nums[i]:

这个思路就是上面“ 01背包、组合”中讲解的,如果是正序就会导致物品被重复取用,就是完全背包问题

2、例题
518. 零钱兑换 II

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 示例 1: 输入: amount
= 5, coins = [1, 2, 5] 输出: 4

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        dp=[0 for i in range(amount+1)]
        dp[0]=1
        for i in range(len(coins)):
            for j in range(coins[i],amount+1):
                dp[j]+=dp[j-coins[i]]
        return dp[-1]
完全背包、排序

1、遍历方式讲解

        for j in range(0,target+1):
            for i in range(len(nums)):
                if j>=nums[i]:

如果求组合数就是外层for循环遍历物品,内层for遍历背包。 如果求排列数就是外层for遍历背包,内层for循环遍历物品。
如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3}
这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!
所以本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。

2、例题
377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
示例 1:
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        dp=[0 for i in range(target+1)]
        dp[0]=1
        for j in range(0,target+1):
            for i in range(len(nums)):
                if j>=nums[i]:
                    dp[j]+=dp[j-nums[i]]
        return dp[-1]

递推公式

求最小值
dp[j]=min(dp[j],dp[j-i**2]+1)

初始化和求最大值不一样,求最大值直接全初始化为0,但是最小值为[0,最大值,最大值,…,最大值]

dp=[n for i in range(n+1)]
dp[0]=0

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。 完全平方数
是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。
例如,1、4、9 和 16 都是完全平方数,而 3 和 11不是。
示例 1:
输入:n = 12
输出:3 解释:12 = 4 + 4 + 4

from math import sqrt
class Solution:
    def numSquares(self, n: int) -> int:
        dp=[n for i in range(n+1)]
        dp[0]=0
        a=ceil(sqrt(n))
        for i in range(1,a+1):
            for j in range(i**2,n+1):
                dp[j]=min(dp[j],dp[j-i**2]+1)
        return dp[-1]
求最大值
dp[j]=max(dp[j],dp[j-nums[i]]+nums[i])

初始化为[0,0,0,…,0]
416. 分割等和子集

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1: 输入:nums = [1,5,11,5]
输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11]

这里取11为背包容量,看看背包能否装满, [1, 5, 5] 将背包装满了,剩下的正好是11,则可以分为两半。

求组合数
dp[j]+=dp[j-nums[i]]

初始化为[1,0,0,…,0]

377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
示例 1:
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        dp=[0 for i in range(target+1)]
        dp[0]=1
        for j in range(0,target+1):
            for i in range(len(nums)):
                if j>=nums[i]:
                    dp[j]+=dp[j-nums[i]]
        return dp[-1]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值