DAY47:动态规划(十)零钱兑换Ⅱ+组合总和Ⅳ(完全背包求方案总数类型,排列+组合)

文章目录

    • 518.零钱兑换Ⅱ(装满背包方案数,本题是组合方案数)
      • 思路
      • DP数组含义
      • 递推公式
      • DP数组初始化
      • 遍历顺序(重要,不能颠倒)
        • 外层物品内层背包的情况
        • 外层背包内层物品的情况
        • 完全背包求排列数和组合数
      • 完整版
      • 总结+面试题目
    • 377.组合总和Ⅳ(本题是排列方案数)
      • 思路
      • DP数组含义
      • 递推公式
      • 初始化
      • 遍历顺序
      • 最开始的写法:int溢出
        • debug测试
      • 防止溢出的方式:对较大的数字取模
        • MOD取模的作用
      • 总结

这两道题都是方案数问题,注意方案数问题背包是装满的

518.零钱兑换Ⅱ(装满背包方案数,本题是组合方案数)

  • 本题就属于完全背包问题,但是for循环不可颠倒的情况
  • 本题求的是组合方案颠倒for的顺序就是排列方案

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。

请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。

假设每一种面额的硬币有无限个。

题目数据保证结果符合 32 位带符号整数。

示例 1:

输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3

示例 3:

输入:amount = 10, coins = [10] 
输出:1

提示:

  • 1 <= coins.length <= 300
  • 1 <= coins[i] <= 5000
  • coins 中的所有值 互不相同
  • 0 <= amount <= 5000

思路

本题提到了”凑成总和“,也就是从数组coins中选取物品,使得物品总重量和为目标和

因此我们第一反应还是背包问题。实际上,背包问题就是与得到某个确定的目标和有关的问题。

加上题目描述中说,假设每一种面额的硬币有无限个,因此这属于完全背包

本题最后返回的结果是可以凑成总金额的硬币组合数,也就是说装满背包一共有多少种方案。这就和 494.目标和 有点像,但是 目标和 是01背包。

本题和纯完全背包的区别在于,纯完全背包是求背包最大价值,而本题是求装满背包方案数。

DP数组含义

DP数组含义是看求的是什么。本题求的是装满背包的方案数,因此dp[j]表示装满容量为j的背包,有dp[j]种方法

递推公式

本题和01背包的 目标和 基本一样,也属于装满背包方案数目,因此也是同样的递推公式:

dp[j]+=dp[j-coins[i]];//装满背包方案数目问题的递推公式模板

DP数组初始化

和 目标和 那道题一样,求方案数目问题,最重要的就是一开始的初始化,因为所有递推都基于dp[0]

从DP数组的含义来考虑:

dp[0]含义是容量是0,多少种方案装满,因此dp[0]=1

从递推公式角度考虑:

dp[0]必须1,因为dp[0] = 1是方案类问题递归公式的基础。如果dp[0] = 0 的话,后面所有推导出来的值都是0了。

遍历顺序(重要,不能颠倒)

纯完全背包问题中,外层是物品还是背包都可以。但是本题,遍历顺序是不能颠倒的

纯完全背包求得装满背包的最大价值是多少,和凑成总和的元素有没有顺序没关系,有顺序也行,没有顺序也行。

但是,本题要求凑成总和的组合数,元素之间明确要求没有顺序。纯完全背包是能凑成总和就行,凑成总和的方案是否有顺序,不在考虑范围内。但是本题是求凑出来的方案个数,且每个方案个数是为组合数,不能有顺序

外层物品内层背包的情况

我们假设coins[0] = 1,coins[1] = 5,背包容量amount是6。

for(int i=0;i<coins.size();i++){//物品外层for(int j=coins[i];j<=amount;j++){dp[j]+=dp[j-coins[i]];}
}

对于物品在外,背包在内的情况:

  1. 先使用硬币1,计算出所有可能的组合。
  2. 然后加入硬币5,计算出所有可能的组合。

遍历物品i的DP数组如下所示:

// 初始化 DP 数组
dp = [1, 0, 0, 0, 0, 0, 0]// 遍历硬币1
dp = [1, 1, 1, 1, 1, 1, 1]// 遍历硬币5
dp = [1, 1, 1, 1, 1, 2, 2]

这个过程中,硬币5是在硬币1的基础上加入的,所以不会出现{5,1}这样的情况,只有{1,5}。

外层背包内层物品的情况

for(int j=0;j<=amount;i++){//背包容量外层for(int i=0;i<coins.size();i++){if(j>=coins[i])dp[j]+=dp[j-coins[i]];}
}

对于背包在外,物品在内的情况:

  1. 对于每一个背包,都尝试使用硬币1和硬币5来填满背包。
  2. 这就意味着在计算一个特定的背包时,可能会有{1,5}和{5,1}两种情况。

推出来的DP数组如下所示:

在这里插入图片描述
我们可以看出来,结果确实是不对的,但是dp方法只能给出方案数,并不能把每个方案都打印出来。如果需要打印每个方案,必须使用回溯来解决,例如组合总和系列问题,因为需要打印组成结果,所以必须是回溯

回溯法的解释:【总结】用树形图和剪枝操作理解完全背包问题中组合数和排列数问题_先遍历物品后遍历背包是组合数_Calculus2022的博客-CSDN博客

对于coins = {1,2}, amount =4,先遍历物品的情况,相当于对先遍历背包的情况进行了剪枝

在这里插入图片描述
引用这张图能看的更清晰,我们如果先遍历物品,是物品1这条线全部遍历完,物品1遍历的时候不会涉及物品2 ,所以相当于剪枝剪掉了物品1底下1 2那种情况。而到了物品2,拆开2的时候,才会考虑2 1的情况。

由此可知,先遍历物品的情况,相当于对原排列数进行了剪枝

完全背包求排列数和组合数

完全背包问题中,物品和背包遍历的顺序决定了在求解时是考虑了物品的排列情况还是组合情况

物品在外,背包在内,得到的是组合方案数目

物品在内,背包在外,得到的是排列方案数目

完整版

class Solution {
public:int change(int amount, vector<int>& coins) {//定义dp数组vector<int>dp(amount+1,0);//初始化dp[0]=1;for(int i=0;i<coins.size();i++){for(int j=coins[i];j<=amount;j++){dp[j]+=dp[j-coins[i]];}}return dp[amount];}
};

总结+面试题目

完全背包问题,在求装满背包有几种方案的时候,认清遍历顺序是非常关键的。

如果求所有装满背包方案的组合数,就是外层for循环遍历物品,内层for遍历背包

如果求所有方案的排列数,就是外层for遍历背包,内层for循环遍历物品

01背包因为物品和背包遍历顺序不能颠倒,所以并不存在排列数和组合数的问题。

如果面试问到for嵌套顺序颠倒计算排列数和组合数的原理,可以这么回答:

物品在外的话,必须考虑完 i 的所有情况之后再考虑 i + 1,所以去掉了他们的排列关系; 而容量在外的话,每个容量的循环中可以尝试所有的物品,就像排列数每个位置可以放任何物品一样,这样就增加了物品的顺序

377.组合总和Ⅳ(本题是排列方案数)

  • 本题需要注意修改Int溢出的问题,dp[j]+=dp[j-nums[i]]可能会发生溢出,此时我们需要让运算结果对一个较大的数字来取模把dp[j]控制在这个较大数字的范围内

给你一个由 不同 整数组成的数组 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)
请注意,顺序不同的序列被视作不同的组合。

示例 2:

输入:nums = [9], target = 3
输出:0

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 1000
  • nums 中的所有元素 互不相同
  • 1 <= target <= 1000

思路

本质是本题求的是排列总和,而且仅仅是求排列总和的个数,并不是把所有的排列都列出来。为此,我们可以使用完全背包DP来解决。

如果本题要把排列都列出来的话,只能使用回溯算法暴力搜索。像39.组合总和 40.组合总和Ⅱ 都是必须列出组合结果,因此只能用回溯。

之前的组合系列:

39.组合总和:(这道题如果求方案数目是完全背包,且为组合数,物品在for外层

在这里插入图片描述
40.组合总和Ⅱ:(这道题求方案数目是01背包

在这里插入图片描述

DP数组含义

本题从示例可以看出,每个数字可以被使用无限次,所以是完全背包

dp[j]的含义是,装满容量为j的背包,有dp[j]种方案。

递推公式

本题依然是方案数目的递推公式:

dp[j]+=dp[j-nums[i]];

初始化

方案数目类,初始化都是dp[0]=1,其余为0

遍历顺序

本题是求排列方案数目而不是组合,完全背包求排列方案数,背包外层遍历,物品内层遍历

具体原因上一道题目有分析。

最开始的写法:int溢出

class Solution {
public:int combinationSum4(vector<int>& nums, int target) {vector<int>dp(target+1,0);dp[0]=1;//求排列数,外层背包容量for(int j=0;j<=target;j++){for(int i=0;i<nums.size();i++){if(j>=nums[i])dp[j]+=dp[j-nums[i]];}}return dp[target];}
};

debug测试

测试结果出现了dp[j]+=dp[j-nums[i]];这一句,两数相加超过了int的情况。

在这里插入图片描述

防止溢出的方式:对较大的数字取模

class Solution {
public:int combinationSum4(vector<int>& nums, int target) {vector<long long>dp(target+1,0);dp[0]=1;long long MOD = INT_MAX; // adding modulo constantfor(int j=0;j<=target;j++){for(int i=0;i<nums.size();i++){if(j>=nums[i]) {dp[j]=(dp[j]+dp[j-nums[i]]) % MOD;}}}return dp[target];}
};

MOD取模的作用

这里注意,我们设置一个MOD,对MOD进行取模,目的就是把结果限制在<=MOD的范围内

因为取模运算,比如a/b,如果a<b的话,得到的就是a本身。如果a>b,得到的就是a/b的余数。

也就是,如果a=INT_MAX+1,b=INT_MAX,那么a%b就=1。

因此取模运算%,是一个限制数据范围的重要方法,它能保证结果始终在一定范围内

如果我们想把结果限定在MOD范围内,我们只需要result%MOD即可

总结

求装满背包有几种方法,递归公式都是一样的,没有什么差别,但关键在于遍历顺序!

本题与518.零钱兑换II就是一个鲜明的对比,一个是求排列,一个是求组合,遍历顺序完全不同。

同时,本题的取模运算也是一个非常重要的点,为了保证dp[j]不溢出,我们令MOD=INT_MAX,然后dp[j]%MOD,就可以dp[j]限制在INT_MAX范围内

这个操作和螺旋矩阵里面的(t+1)%4操作很像,螺旋矩阵中t初始值为0t=(t+1)%4能让无论多大的t,都在0 1 2 3这四个数字里面循环取值。要特别注意这种用法。(t只需要赋初值,别的都不需要管

[螺旋矩阵题目的两种解法及注意点_大磕学家ZYX的博客-CSDN博客](

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/22633.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

产品流程图

流程图设计 1.什么是流程图2.流程图元素定义3.几种常见的产品流程图 3.1业务流程图 | 泳道图&#xff08;给产品经理看&#xff09;3.2任务流程图&#xff08;给程序员看&#xff09;3.3页面流程图&#xff08;给UI设计人员看&#xff09; 4.如何绘制流程图 4.1调查研究4.2梳理…

S32K系列MCU介绍和资料搜集

1. S32K系列微控制器概述 S32K系列微控制器&#xff0c;是NXP推出的专门面向汽车电子和工业应用场合的微控制器。基于ArmCortex-M系列的可扩展、低功耗微控制器&#xff0c;获得了AEC-Q100认证&#xff0c;具有高级功能安全、信息安全和软件支持&#xff0c;适用于工业和汽车A…

身份证读卡器安卓SDK在安卓12版本targetSdkVersion=32报错解决办法

之前的东信智能的EST-100身份证读卡器安卓SDK版本V1.0.40在安卓12版本&#xff0c;targetSdkVersion32的时候会出现以下错误&#xff1a; Targeting S (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingInten…

利用缓存模拟进度条(c实现)

代码如下&#xff1a; #include <stdio.h> #include <Windows.h>int main() {int total 100;int progress 0;char progressBar[101] {};while (progress < total) {progressBar[progress] #;printf("[%-100s] %d%%", progressBar, progress);ffl…

【UE4】在控件蓝图上播放视频

UE版本&#xff1a;4.26 在上一篇文章中&#xff08;【UE】场景内播放视频、音频&#xff09;介绍了如何在场景中播放视频&#xff0c;本篇文章将介绍如何在UI上播放视频 效果 步骤 1. 首先在“Content”文件夹中新建一个名为“Movies”的文件夹 2. 在文件夹中随便添加一个.…

apache ranger

简介&#xff1a; ranger 是一个用于启用、监控和管理跨hadoop平台的全面的数据安全框架。 ranger的愿景是在hadoop系统中提供全面的安全管理。随着yarn的出现&#xff0c;hadoop 平台能够支持真正的数据糊架构。企业能够在多租户环境中运行多个任务负载。hadoop 数据安全需要…

在阿里云linux上安装MySql数据库

我们先远程连接服务器 然后输入 sudo yum update重新运行一下 然后 sudo yum install mysql-server安装 mysql 服务 其中有两次 y n 选择 都选y就好了 然后 运行 sudo service mysqld start启动MySql 然后 我们查看一下MySql sudo service mysqld status

用github的copilot;tmux中进去了> 怎么退出

1、首先要学籍认证 &#xff08;前提&#xff1a;(241条消息) Copilot使用的关卡——GitHub教育认证方法和注意事项_github教师认证_石去皿的博客-CSDN博客&#xff09; 网址&#xff1a;Are you a student? - GitHub Education (241条消息) GitHub学生认证&#xff0c;可…

Linux内核模块开发 第 10 章 系统调用

The Linux Kernel Module Programming Guide Peter Jay Salzman, Michael Burian, Ori Pomerantz, Bob Mottram, Jim Huang译 断水客&#xff08;WaterCutter&#xff09;源 LKMPG 10 系统调用 到目前为止&#xff0c;我们所做的唯一一件事就是使用定义明确的内核机制来注册…

Android 多边形导圆角(Path画折线导圆角)

前言&#xff1a;用path画折线&#xff0c;2条线相交处导圆角 简介&#xff1a;为开发者提供工具类&#xff0c;方便对使用path画折线设置圆角。使用path画多边形&#xff0c;如三角形、矩形、平行四边形等&#xff0c;同时设置圆角。另外提供计算直线上某点坐标。 实现原理 …

开源大模型发展汇总

1. 大模型发展&概况 a. 发展线路图 其中基础模型如下&#xff1a; 大部分不开源&#xff0c;而OPT、BLOOM、LLaMA 三个模型是主要面向开源促进研究&#xff0c;聊天机器人场景开源的Open Assistant&#xff08;huggingface&#xff09;中文有一些GLM&#xff0c;百川&…

Mac环境下安装nginx并本地部署项目

1、前提 必须安装了homebrew&#xff0c;可在终端输入命令brew -v查看是否已经安装&#xff0c;如果输入指令出现版本号说明已经安装成功 如果未安装先安装&#xff08;homebrew官网地址&#xff09; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/H…