【动态规划Ⅵ】背包问题 /// 组合问题

背包问题

  • 什么是背包问题
    • 0-1背包问题
    • 分数背包
    • 完全背包问题
    • 重复背包问题
  • 背包问题例题
    • 416. 分割等和子集
    • 474. 一和零
  • 完全平方数
    • 279. 完全平方数
    • 322. 零钱兑换
  • 排列与组合
    • 组合,无重复:518. 零钱兑换 II
    • 排列,可重复:377. 组合总和 Ⅳ

什么是背包问题

有一个容量为W的背包;现在有一些货品(体积wi,价值vi),总数为n。如果将一个货品放入背包,会消耗掉背包wi的空间,但同时会收获vi的价值。现在需要一个方案,选择一些物品,使背包能够装下,同时,收获的价值最大。这个问题就是背包问题~

0-1背包问题

0-1背包问题是指针对一个货品,不能拆分,即如果选择装入背包,就要一整个装入,会占题集wi,收获价值vi;不能只装一部分;同时每个物品被选择的话,不能选择一次!
那么针对0-1背包,其实可以找所有物品的组合,在能够满足货品和<W的组合里面,价值最大的一个组合即可。 但是如果货品很多,组合就有2^n,搜索空间很大。
这里用动态规划求解,首先分析状态转移方程。用一个二维数组dp表示各个货品i,在背包重量为j的情况下(j<W),能够装下货品的最大价值,行对应每个货品,列对应可能的背包重量。
如果当前背包容量j < wi,那么背包根本装不下货品i,此时dp[i][j] = dp[i-1][j]。在当前背包容量j > wi的前提下,针对一个货品有两个方案:

  • ①不装入:那么不消耗背包体积,也不增加背包货品价值,可得dp[i][j] = dp[i-1][j]
  • ②装入:装入会获得价值vi,但也会占一定的背包体积,剩余背包体积为j- wi,再去找1~i-1(即货品i前面的货品)在背包容量为j-wi的情况下,能够获得的最大价值,dp[i][j] = dp[i-1][j - wi] + vi

但是别忘了,我们是要找最大值,那么就是要将装入or不装入两种情况的dp[i][j]对比,选最大;且只有在w>wi的时候,才有两种选择,因此最终:if(j > wi): dp[i][j] = max( dp[i-1][j] , dp[i-1][j - wi] + vi); else: dp[i][j] = dp[i-1][j]。我们遍历dp这个二维数组,更新dp[i][j]即可,最终结果就是的dp[n][W]。
在这里插入图片描述

我们可以在空间上进行优化,考虑用一维dp进行状态转移。在j>wi时,货品i可能被选择装入,我们更新dp[i][j]的时候,在列上会选择j-wi的列,即dp[i-1][j-wi],事实时j - wi < j,是j前面的列。因此当我改用一维dp进行状态转移的时候,需要从j = W开始往前遍历,因为前面的列,我们在状态转移的时候需要用到,它们需要维持没考虑货品i时的状态。最终状态转移:dp[j] = max(dp[j], dp[j - wi])

分数背包

分数背包和0-1背包不同的点,在于货品可以拆分,假设在货品i前面加一个系数k(范围是[0,1]),那么状态转移如下:
在这里插入图片描述

完全背包问题

完全背包和0-1背包不同的点,在于每个货品有无限个,可以选择多个,但是不能多于k = W/wi,那么状态转移方程如下:
在这里插入图片描述

重复背包问题

重复背包和完全背包不同的是,重复背包给每个货品限制了最多可以选择n_i,其状态转移方程如下:
在这里插入图片描述

背包问题例题

下面两个问题是经典的背包问题!

416. 分割等和子集

416. 分割等和子集——题目内容如下👇

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

理解题目,转换一下👉在数组中找一些元素(一个子集),使其和为所有元素之和(sum)的一半(target)。这就转换成背包问题了。但不同的点在于:背包问题是<=W,然后找价值v的最大值;这里就是找和=target。
还是用一维dp表示状态转移【节省空间,二维也是可以的】,这个dp是一个boolean的一维数组,dp[i]表示是否有子集和为i。针对nums中的每一个元素num,有两种情况:①选择,那么就要去判断num之前的元素,能不能选出几个使其和为i-num? 即dp[i] = dp[ i - num];②不选择,dp[i] = dp[i];这两种情况只要有一个为true,即可认为nums中有子集和可以为i。因此dp[i] = dp[i] || dp[i-num]
这里有一些可以提前判断的情况:

  • 所有元素和sum为奇数,那么必不可能有子集为其一半,因为分不出一半;
  • nums只有一个元素的时候,且元素不能为0,无法拆分成两个子集了。
  • nums中有一个最大元素maxNum,该元素大于sum的一半,sum - maxNum < maxNum,即除去maxNum剩余元素之不可能等于maxNum,maxNum也不能拆分,直接返回false。

dp初始化全为false,dp[0] = true用于判断边界:某一元素num = i时,dp[i-num] = dp[0] =true。 Java版本代码如下:

class Solution {public boolean canPartition(int[] nums) {int len = nums.length;if(len < 2)return false;int sum = 0, maxNum = 0;for(int i = 0; i < len; i++){sum += nums[i];maxNum = Math.max(maxNum, nums[i]);}if(sum % 2 == 1 || sum - maxNum < maxNum)return false;int target = sum / 2;boolean[] dp = new boolean[target + 1];dp[0] = true;for(int i = 0; i < len; i++){for(int j = target ; j >= nums[i]; j--){dp[j] = dp[j] || dp[j - nums[i]];}}return dp[target];}
}

474. 一和零

474. 一和零 ——题目内容如下👇

给你一个二进制字符串数组 strs 和两个整数 mn
请你找出并返回 strs 的最大子集的长度,该子集中 最多m 个 0n 个 1
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

根据题意,会发现这就是背包问题!背包的容量W,在这里就是m个0和n个1。相当于原来的背包只有容量W一个限制,现在有两个限制。背包问题用一个二维数组记录各个状态,那么这里需要三维数组。同样的,可以进行空间优化,因此用二维数组dp表示状态转移,dp[i][j]表示能够满足包括少于等于i个0,少于等于j个1的最大子集长度。对于strs中的每一个字符串str,有两种可能:①选择,那么对应的长度为dp[i][j] = dp[i-zeros][j-ones] + 1;②不选择,那么dp[i][j] = dp[i][j]。当然,选择str的前提是,i >= zeros,j >= ones。因此得到最终的状态转移过程:

  • dp[i][j] = max( dp[i][j] ,dp[i-zeros][j-ones]), if i >= zeros,j >= ones
  • dp[i][j] = dp[i][j], otherwise

注意,用二维数组进行状态转移时,针对strs中每个str会更新一轮数组,更新规则是从右下角开始的,从后往前,从下往上。理由是i-zeors < i,j-ones < j,之前的行和列需要保持上一轮状态。Java代码如下:

class Solution {public int findMaxForm(String[] strs, int m, int n) {int[][] subNum = new int[m+1][n+1];int len = strs.length;for(int i = 0; i < len; i++){int ones = getOnes(strs[i]);int zeros = strs[i].length() - ones;for(int j = m; j >= zeros; j--)for(int k = n; k >= ones; k--)subNum[j][k] = Math.max(subNum[j][k], subNum[j - zeros][k -ones] +1);}return subNum[m][n];}public int getOnes(String str){int ones =0;int len = str.length();for(int i = 0; i < len; i++)ones += (str.charAt(i) - 48);return ones;}
}

完全平方数

下面的两个题目其实就是一维的动态规划,不是严格的背包问题。但是两个题目后下面排列组合的问题有关联。 且两个题目非常的相似,因从也整理在这里了。

279. 完全平方数

279. 完全平方数

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

既然是动态规划,我们用一个一维数组dp来表示转换过程。dp[i]即表示和为i的完全平方数的最少数量。对于整数i,待选的完全平方数j的范围是1~sqrt(i),那么就遍历每一个j,然后找到dp[i - j*j]里面的最小者,然后再+1,就是dp[i]了:dp[i] = 1 + min(dp[i - j\*j] , 1<=j <=sqrt(i)。相当于假设遍历可能参与相加使得和为i的完全平方数j,这个j参与相加,还需要找一些完全平方数使其和为i- j*j,dp[i - j*j]已经找到了;目的是找最少数量,因此要找最小的dp[i-j*j]。
java代码如下:

class Solution {public int numSquares(int n) {int[] dp = new int[n+1];dp[0]=0;for(int i = 1; i <= n; i++){int minFront = Integer.MAX_VALUE;for(int j = 1; j*j <= i;j++)minFront = Math.min(minFront, dp[i - j*j]);dp[i] = 1 + minFront;}return dp[n];}
}

322. 零钱兑换

322. 零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数amount ,表示总金额
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。

其实这个题目和上面的完全平方数非常的类似,完全平方数中供选择的是完全平方数j,且j*j <i;这个题目可供选择的是coins中的硬币coin,且coin < i。同样用一维dp表示状态转移,dp[i]表示用coins中最少数量的硬币,组成i元。遍历coins中的小于i的硬币coin:dp[i] = min( dp[i - coin]) + 1。
完全平方数中,不论怎么样,都可以由无数的1组成,但是这个coins中的硬币可能没有1,那么有些i可能就没有硬币组合方案,结果为-1。考虑初始化dp的时候将其设置成amount + 1,因为就算是全部由1组成也只需要amount个1,这样如果dp[i] = amount + 1 就表示没有方案。
java代码如下:

class Solution {public int coinChange(int[] coins, int amount) {int[] minCur = new int [amount+1];int max = amount + 1;Arrays.fill(minCur, max);// 先将coins排序,后续不用对所有i都遍历一整个coins数组了Arrays.sort(coins);  minCur[0] = 0;int n = coins.length;for(int i = 1; i <= amount; i++)for(int j = 0; j < n && coins[j] <= i; j++ ){minCur[i] = Math.min(minCur[i], minCur[i-coins[j]] + 1);}return minCur[amount] > amount ? -1: minCur[amount];}
}

排列与组合

排列和组合的区别,排列考虑位置,组合不考虑位置。排列中(1,2)与(2,1)是不一样的,但是组合中这两个就是重复的。

组合,无重复:518. 零钱兑换 II

518. 零钱兑换 II

给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。

这个题目是返回硬币的组合数,即组合方案数量。一开始我的想法是:对于coins中的coin,更新的dp[i],dp[i] = sum( dp[i - coin] ),但这是有问题的。比如coins = [1,2],amount = 3,dp[0] = 1, dp[1] = dp[1-1] = 1, dp[2] = dp[2-1] + dp[2-2] =2, dp[3] = dp[3-1] + dp[3-2] = 3。
结果是错误的,因此dp[3-1]考虑了1 + 2和 1 +1 +1,但是dp[3-2]又考虑了2 + 1,里面就有重复。
正确的避免这种重复的办法是,固定住待选择的零钱集合,假设一开始参与组合的只有coin[0],然后是{coin[0],coin[1]},然后是{coin[0],coin[1],coin[2]}依次增加一个硬币coin[i],这样做就避免了i < j时,x = coin[i] + coin[j] 与coin = coin[j] + coin[i]这种情况,因为候选硬币为coin[0~i]的时候,coin[j]没有参与!
整理一下,就是依次遍历coins中的硬币coin,考虑加入coin这个候选硬币后,新增的组合数:dp[i] += dp[i - coin]
java代码如下:

class Solution {public int change(int amount, int[] coins) {int[] num = new int[amount + 1];int n = coins.length;num[0] = 1;//排序不排序都一样的Arrays.sort(coins);  //一次新增coin[i]这个候选硬币for(int i = 0; i < n; i++){for(int j = coins[i];j <= amount; j++)  num[j] += num[j - coins[i]];}return num[amount];}
}

排列,可重复:377. 组合总和 Ⅳ

377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
在这里插入图片描述

只看题目文字,会发现和零钱兑换不是一模一样吗!但是一看例子,原来这个是数字顺序不一样就行,那就是排列问题!那么遍历coins中的每个coin,dp[i] = sum(dp[i - coin]),其实遍历coins中的coin,选中一个coin的时候,就相当于固定了这个coin的位置,那么就是一种排列,比如 3 = 1 + 2,3 = 2 + 1,前面一种先固定了coin = 1,第二种是先固定了coin = 2。
java代码如下:

class Solution {public int combinationSum4(int[] nums, int target) {int n = nums.length;int[] count = new int[target+1];count[0] = 1;Arrays.sort(nums);for(int i = 1; i <= target; i++){for(int j = 0; j < n && nums[j] <= i; j++)count[i] += count[i - nums[j]];}return count[target];}
}

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

【效率提升】多功能组织和整理软件一Notion

Notion下载地址&#xff1a;https://www.notion.so/desktop Notion 是一款多功能办公软件&#xff0c;非常适合进行知识整理、团队协作、项目整理等。 下面是几个我推荐Notion的关键点&#xff1a; 一、排版高度自由&#xff1a;Notion的页面可以嵌套&#xff0c;在页面中还支…

Games101——光珊化——深度缓存——shading着色 1

深度缓存 如何解决远近的问题&#xff0c;能正确的覆盖 按照画作来说&#xff0c;先画出远处的物体&#xff0c;再画出近处的物体&#xff0c;近处会将其覆盖&#xff0c;这种算法叫做画家算法 但事实上&#xff0c;排序不仅要花更多的时间&#xff0c;而且排序并不容易&…

apache:the requested operation has failed使用httpd -t

Apache24\bin cmd 回车 httpd -t 因为我重新压缩了&#xff0c;记住&#xff0c;重新压缩要使用原路径&#xff0c; 因为你安装的 时候使用的是原路径 还是不行就改个端口&#xff0c;切记修改配置文件httpd.conf先把Tomcat停了 Define SRVROOT "F:\Apache\Apache24&q…

“Numpy数据分析与挖掘:高效学习重点技能“

目录 # 开篇 # 补充 zeros & ones eye 1. numpy数组的创建 1.1 array 1.2 range 1.3 arange 1.4 常见的数据类型 1.5 astype 1.6 random.random() & round 2. numpy数组计算和数组计算 2.1 reshape 2.2 shape 2.3 将一维数组变成多维数组 2.4 指定一维…

生产者消费者模型和线程同步问题

文章目录 线程同步概念生产者消费者模型条件变量使用条件变量唤醒条件变量 阻塞队列 线程同步概念 互斥能保证安全,但是仅有安全不够,同步可以更高效的使用资源 生产者消费者模型 下面就基于生产者消费者来深入线程同步等概念: 如何理解生产消费者模型: 以函数调用为例: 两…

14-58 剑和诗人32 - 使用矢量数据库增强 LLM 应用程序

GPT-4、Bloom、LaMDA 等大型语言模型 (LLM) 在生成类似人类的文本方面表现出了令人印象深刻的能力。然而,它们在事实准确性和推理能力等方面仍然面临限制。这是因为,虽然它们的基础是从大量文本数据中提取统计模式,但它们缺乏结构化的知识源来为其输出提供依据。 最近,我们…

红日靶场----(三)漏洞利用

上期已经信息收集阶段已经完成&#xff0c;接下来是漏洞利用。 靶场思路 通过信息收集得到两个吧靶场的思路 1、http://192.168.195.33/phpmyadmin/&#xff08;数据库的管理界面&#xff09; root/root 2、http://192.168.195.33/yxcms/index.php?radmin/index/login&am…

(自用)gtest单元测试

gtest是Google的一套用于编写C测试的框架&#xff0c;可以运行在很多平台上&#xff08;包括Linux、Mac OS X、Windows、Cygwin等等&#xff09;。基于xUnit架构。支持很多好用的特性&#xff0c;包括自动识别测试、丰富的断言、断言自定义、死亡测试、非终止的失败、生成XML报…

Python | Leetcode Python题解之第225题用队列实现栈

题目&#xff1a; 题解&#xff1a; class MyStack:def __init__(self):"""Initialize your data structure here."""self.queue collections.deque()def push(self, x: int) -> None:"""Push element x onto stack."&…

14-59 剑和诗人33 - 端到端的LLMOps平台建设

​​​​ GPT-4、LlaMA、Falcon、Claude、Cohere、PaLM 等大型语言模型 (LLM) 已展示出强大的自然语言生成、推理、摘要、翻译等功能。然而&#xff0c;要有效利用这些模型来构建自定义应用程序&#xff0c;需要克服非同小可的机器学习工程挑战。 LLMOps 旨在提供一个精简的平…

从零开始学习嵌入式----C语言框架梳理与后期规划

目录 一、环境搭建. 二、见解 三、C语言框架梳理 四、嵌入式学习规划流程图&#xff08;学习顺序可能有变&#xff09; 一、环境搭建. C语言是一门编程语言&#xff0c;在学习的时候要准备好环境。我个人比较喜欢用VS,具体怎么安装请百度。学习C语言的时候&#xff0c;切忌…

clean code-代码整洁之道 阅读笔记(第十七章 终章)

大纲 第十七章 味道与启发 17.1 注释 C1&#xff1a;不恰当的信息 C2&#xff1a;废弃的注释 C3&#xff1a;冗余注释 C4&#xff1a;糟糕的注释 C5&#xff1a;注释掉的代码 17.2 环境 E1&#xff1a;需要多步才能实现的构建 E2&#xff1a;需要多步才能做到的测试 …

中科驭数HADOS 3.0:以四大架构革新,全面拥抱敏捷开发理念,引领DPU应用生态

一家成功的大算力芯片公司&#xff0c;其核心壁垒必须是软硬兼备的&#xff0c;既要有芯片架构的强大技术壁垒&#xff0c;更要有持久投入的、生态兼容完备的软件护城河。HADOS&#xff0c;就是中科驭数的软件护城河。 ——中科驭数高级副总裁 张宇 近日&#xff0c;在DPU基础…

STM32F103RB多通道ADC转换功能实现(DMA)

目录 概述 1 硬件 1.1 硬件实物介绍 1.2 nucleo-f103rb 1.3 软件版本 2 软件实现 2.1 STM32Cube配置参数 2.2 项目代码 3 功能代码实现 3.1 ADC功能函数 3.2 函数调用 4 测试 4.1 DMA配置data width&#xff1a;byte 4.2 DMA配置data width&#xff1a;Half wor…

调整网络安全策略以适应不断升级的威胁形势

关键网络安全统计数据和趋势 当今数字时代网络安全的重要性

vscode如何运行php代码

安装PHP扩展&#xff1a; 打开VSCode&#xff0c;进入扩展视图&#xff08;或使用快捷键CtrlShiftX&#xff09;。搜索并安装“PHP Intelephense”或“PHP Server”等PHP相关扩展&#xff0c;这些扩展提供了PHP代码的智能感知、代码导航、调试等功能。 配置PHP路径&#xff1a;…

系统架构设计师教程 第二章 计算机系统基础知识-2.3计算机软件

系统架构设计师教程 第二章 计算机系统基础知识-2.3计算机软件 2.3 计算机软件2.3.1 计算机软件概述2.3.2 操作系统2.3.2.1 操作系统的组成2.3.2.2 操作系统的作用2.3.2.3 操作系统的特征2.3.2.4 操作系统的分类2.3.3 数据库2.3.3.1 关系数据库2.3.3.1.1 关系数据库设计的特点及…

js原型和类---prototype,__proto__,new,class

原型和原型链 在js中&#xff0c;所有的变量都有原型&#xff0c;原型也可以有原型&#xff0c;原型最终都指向Object 什么是原型 在js中&#xff0c;一个变量被创建出来&#xff0c;它就会被绑定一个原型&#xff1b;比如说&#xff0c;任何一个变量都可以使用console.log打…

Simulink生成代码时端口名称乱码问题

写在最前&#xff1a; 在使用Simulink生成代码时发现端口名称与模型中定义的输如输出端口名称不一致&#xff0c;代码生成的端口名称为随机字符名称。 在生成的H文件中发现&#xff0c;端口定义的结构体名称与模型中实际定义的名称不符。 模型中的定义 检查后发现&#xff0c…

重点区域分布式无人机探测防御系统详解

重点区域分布式无人机探测防御系统采用模块化设计&#xff0c;结合先进的传感技术、通信技术和数据处理技术&#xff0c;实现对无人机的高效探测与防御。系统架构由无人机探测模块、数据分析中心、防御与反制模块以及用户交互界面四大部分组成。系统特点包括分布式部署、高灵敏…