算法沉淀——动态规划之完全背包问题(leetcode真题剖析)

在这里插入图片描述

算法沉淀——动态规划之完全背包问题

  • 01.【模板】完全背包
  • 02.零钱兑换
  • 03.零钱兑换 II
  • 04.完全平方数

完全背包问题是背包问题的一种变体,与01背包问题不同,它允许你对每种物品进行多次选择。具体来说,给定一个固定容量的背包,一组物品,每个物品有重量和价值,目标是找到在背包容量范围内,使得背包中的物品总价值最大的组合。

相较于01背包问题,完全背包问题允许对每个物品进行多次选择,即每个物品都有无限件可用。

动态规划解法

  1. 定义状态: 通常使用二维数组dp[i][j]表示在前i个物品中,背包容量为j时的最大总价值。

  2. 状态转移方程: 考虑第i个物品,可以选择放入背包或者不放入。如果选择放入,那么总价值为dp[i][j-weight[i]] + value[i],即前i个物品的总价值加上当前物品的价值。如果选择不放入,那么总价值为dp[i-1][j],即前i-1个物品的总价值。因此,状态转移方程为:

    dp[i][j] = max(dp[i-1][j], dp[i][j-weight[i]] + value[i])
    

    其中,dp[i-1][j]表示不放入第i个物品,dp[i][j-weight[i]] + value[i]表示放入第i个物品。

  3. 初始条件:i=0时,表示前0个物品,总价值为0;当j=0时,表示背包容量为0,总价值也为0。

  4. 遍历顺序: 外层循环遍历物品,内层循环遍历背包容量。

  5. 返回结果: 最终结果存储在dp[N][W]中,其中N为物品数量,W为背包容量。

例子

假设有如下物品:

物品1:重量=2,价值=3
物品2:重量=3,价值=4
物品3:重量=4,价值=5

背包容量为W=8,我们要求解在这个条件下的最大总价值。

按照上述动态规划解法,构建状态转移表如下:

重量/价值      0   1   2   3   4   5   6   7   8
  ----------------------------------------------
  物品0        0   0   0   0   0   0   0   0   0
  物品1        0   0   3   6   9   12  15  18  21
  物品2        0   0   3   6   9   12  15  18  21
  物品3        0   0   3   6   9   12  15  18  21

因此,最终结果为dp[3][8] = 21,表示在背包容量为8的情况下,最大总价值为21。这意味着最优解是选择物品1,物品2和物品3各两件放入背包。

01.【模板】完全背包

题目链接:https://www.nowcoder.com/practice/237ae40ea1e84d8980c1d5666d1c53bc?tpId=230&tqId=2032575&ru=/exam/oj&qru=/ta/dynamic-programming/question-ranking&sourceUrl=%2Fexam%2Foj%3Fpage%3D1%26tab%3D%25E7%25AE%2597%25E6%25B3%2595%25E7%25AF%2587%26topicId%3D196

描述

你有一个背包,最多能容纳的体积是V。

现在有n种物品,每种物品有任意多个,第i种物品的体积为vi,价值为wi。

(1)求这个背包至多能装多大价值的物品?

(2)若背包恰好装满,求至多能装多大价值的物品?

输入描述

第一行两个整数n和V,表示物品个数和背包体积。

接下来n行,每行两个vi和wi表示第i种物品的体积和价值。

1≤n,V≤1000

输出描述

输出有两行,第一行输出第一问的答案,第二行输出第二问的答案,如果无解请输出0。

示例1

输入:

2 6
5 10
3 1

输出:

10
2

示例2

输入:

3 8
3 10
9 1
10 1

输出:

20
0

说明:

无法恰好装满背包。

示例3

输入:

6 13
13 189
17 360
19 870
14 184
6 298
16 242

输出:

596
189

说明:

可以装5号物品2个,达到最大价值298*2=596,若要求恰好装满,只能装1个1号物品,价值为189.

思路

第一问:

  1. 状态表示:
    • dp[i][j] 表示从前 i 个物品中挑选,总体积不超过 j,所有选法中能挑选出的最大价值。
  2. 状态转移方程:
    • 根据最后一步的状况,分情况讨论:
      • 0 个第 i 个物品:相当于去前 i - 1 个物品中挑选,总体积不超过 j,最大价值为 dp[i - 1][j]
      • 1 个第 i 个物品:相当于去前 i - 1 个物品中挑选,总体积不超过 j - v[i]。此时最大价值为 dp[i - 1][j - v[i]] + w[i]
    • 综上,状态转移方程为:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i])
  3. 初始化:
    • 多加一行,将第一行初始化为 0,因为什么也不选时,满足体积不小于 j 的情况,此时价值为 0
  4. 填表顺序:
    • 从上往下填表。
  5. 返回值:
    • 根据状态表示,返回 dp[n][V]

第二问:

  1. 状态表示:
    • dp[i][j] 表示从前 i 个物品中挑选,总体积正好等于 j,所有选法中能挑选出来的最大价值。
  2. 状态转移方程:
    • dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i])
    • 在使用 dp[i][j - v[i]] 时,需要判断 j >= v[i]dp[i][j - v[i]] 表示的状态是否存在,即 dp[i][j - v[i]] != -1
  3. 初始化:
    • 多加一行,将第一个格子设置为 0,因为正好能凑齐体积为 0 的背包;但是第一行后面的格子都设置为 -1,因为没有物品,无法满足体积大于 0 的情况。
  4. 填表顺序:
    • 从上往下填表。
  5. 返回值:
    • 由于最后可能凑不成体积为 V 的情况,因此返回之前需要特判一下。

空间优化:

对于背包问题,一般都可以使用「滚动数组」来进行空间上的优化,即减少状态表示的维度。

在 01 背包问题中,优化的结果为:

  1. 删掉所有的横坐标。
  2. 修改一下 j 的遍历顺序。

这样的优化是因为在计算 dp[i][j] 时,只依赖于上一行 dp[i-1][j]dp[i-1][j-v[i]],而 dp[i-1][j-v[i]] 在当前行的计算过程中已经被更新过,因此不需要保留整个二维数组。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int N=1002;
int n,V,v[N],w[N];
int dp[N][N];

int main() {
    cin>>n>>V;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];

    for(int i=1;i<=n;i++)
        for(int j=0;j<=V;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j>=v[i]) dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
        }
    cout<<dp[n][V]<<endl;

    memset(dp,0,sizeof dp);
    for(int j=1;j<=V;j++) dp[0][j]=-1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=V;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j>=v[i]&&dp[i][j-v[i]]!=-1) 
                dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
        }
    cout<<(dp[n][V]==-1?0:dp[n][V])<<endl;

    return 0;
}

02.零钱兑换

题目链接:https://leetcode.cn/problems/coin-change/

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

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1

示例 2:

输入:coins = [2], amount = 3
输出:-1

示例 3:

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

提示:

  • 1 <= coins.length <= 12
  • 1 <= coins[i] <= 231 - 1
  • 0 <= amount <= 104

思路

  1. 状态表示:
    • dp[i][j] 表示从前 i 个硬币中挑选,总和正好等于 j,所有选法中最少的硬币个数。
  2. 状态转移方程:
    • 在完全背包问题中,每个硬币可以选无限个,因此需要分多种情况讨论:
      • 0 个第 i 个硬币:相当于去前 i - 1 个硬币中挑选,总和正好等于 j。此时最少的硬币个数为 dp[i - 1][j]
      • 1 个第 i 个硬币:相当于去前 i - 1 个硬币中挑选,总和正好等于 j - coins[i]。因为挑选了一个第 i 个硬币,此时最少的硬币个数为 dp[i][j - coins[i]] + 1
    • 综上,状态转移方程为:dp[i][j] = min(dp[i - 1][j], dp[i][j - coins[i]] + 1)
  3. 初始化:
    • 初始化第一行,将第一个位置设置为 0,因为正好能凑齐总和为 0 的硬币;其余位置设置为无穷大。
  4. 填表顺序:
    • 从上往下填表。
  5. 返回值:
    • 根据状态表示,返回 dp[n][V]。但要特判一下,因为有可能凑不到。

代码

class Solution {
    const int INF=0x3f3f3f3f;
public:
    int coinChange(vector<int>& coins, int amount) {
        int n=coins.size();
        vector<vector<int>> dp(n+1,vector<int>(amount+1));

        for(int j=1;j<=amount;j++) dp[0][j]=INF;
        for(int i=1;i<=n;i++)
            for(int j=0;j<=amount;j++)
            {
                dp[i][j]=dp[i-1][j];
                if(j>=coins[i-1]) dp[i][j]=min(dp[i][j],dp[i][j-coins[i-1]]+1);
            }
        return dp[n][amount]>=INF?-1:dp[n][amount];
    }
};

03.零钱兑换 II

题目链接:https://leetcode.cn/problems/coin-change-ii/

给你一个整数数组 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

思路

  1. 状态表示:
    • dp[i][j] 表示从前 i 个硬币中挑选,总和正好等于 j,一共有多少种选法。
  2. 状态转移方程:
    • 在完全背包问题中,每个硬币可以选无限个,因此需要分多种情况讨论:
      • 0 个第 i 个硬币:相当于去前 i - 1 个硬币中挑选,总和正好等于 j。此时的选法数为 dp[i - 1][j]
      • 1 个第 i 个硬币:相当于去前 i - 1 个硬币中挑选,总和正好等于 j - coins[i]。因为挑选了一个第 i 个硬币,此时的选法数为 dp[i][j - coins[i]] + 1
    • 综上,状态转移方程为:dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]] + 1
  3. 初始化:
    • 初始化第一行,表示没有物品,总和正好为 0 的情况。只有一种情况,即 dp[0][0] = 1;其余位置都为 0 种情况。
  4. 填表顺序:
    • 从上往下填表。
  5. 返回值:
    • 根据状态表示,返回 dp[n][V]

代码

class Solution {
public:
    int change(int amount, vector<int>& coins) {
        int n=coins.size();
        vector<vector<int>> dp(n+1,vector<int>(amount+1));
        dp[0][0]=1;
        for(int i=1;i<=n;i++)
            for(int j=0;j<=amount;j++){
                dp[i][j]=dp[i-1][j];
                if(j>=coins[i-1]) dp[i][j]=dp[i][j]+dp[i][j-coins[i-1]];
            }
        return dp[n][amount];
    }
};

04.完全平方数

题目链接:https://leetcode.cn/problems/perfect-squares/

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,14916 都是完全平方数,而 311 不是。

示例 1:

输入:n = 12
输出:3 
解释:12 = 4 + 4 + 4

示例 2:

输入:n = 13
输出:2
解释:13 = 4 + 9 

提示:

  • 1 <= n <= 104

思路

  1. 状态表示:

    • 在这个问题中,状态表示我们需要找到使得和为 n 的最少完全平方数的数量。因此,我们可以定义状态 dp[i][j],其中 i 表示使用前 i 个完全平方数,j 表示目标和为 jdp[i][j] 的值表示使用前 i 个完全平方数达到和为 j 时的最小数量。
  2. 状态转移方程:

    • 根据问题的特点,我们可以得到状态转移方程:

      dp[i][j]=min(dp[i][j],dp[i][j-i*i]+1);

      其中,i*i表示第 i 个完全平方数。

  3. 初始化:

    • 在初始化阶段,我们需要初始化第一行和第一列的值。对于第一行,因为使用零个完全平方数就能达到和为 0,所以 dp[0][0] = 0。对于其余的 dp[0][j],由于没有完全平方数可用,我们设为一个较大的值(代表不可能达到这个和)。对于第一列,因为使用任何完全平方数都可以达到和为 0,所以 dp[i][0] = 0
  4. 填表顺序:

    • 遍历顺序通常是根据状态转移方程中的依赖关系来确定的。在这里,我们可以先遍历使用的完全平方数 i,然后遍历目标和 j
  5. 返回值:

    • 返回结果是在最后一行 dp[m][n] 中,其中 m 表示完全平方数的个数,n 表示目标和。

代码

class Solution {
    const int INF=0x3f3f3f3f;
public:
    int numSquares(int n) {
        int m=(int)sqrt(n);
        vector<vector<int>> dp(m+1,vector<int>(n+1));
        for(int j=1;j<=n;j++) dp[0][j]=INF;

        for(int i=1;i<=m;i++)
            for(int j=0;j<=n;j++){
                dp[i][j]=dp[i-1][j];
                if(j>=i*i) dp[i][j]=min(dp[i][j],dp[i][j-i*i]+1);
            }
        return dp[m][n];
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【DUSt3R】2张图2秒钟3D重建

【DUSt3R】2张图2秒钟3D重建 1. DUSt3R是一种用于稠密和无约束立体三维重建的方法,其实现步骤如下:2. 实际运行效果3. 运行结果4. 自问自答4.1 为社么这里要是使用transform模型呢?4.2 CroCo(通过跨视图完成3D视觉任务的自我监督预训练的一个研究)在DUSt3R的作用是什么,为…

如何在CentOS部署JumpServer堡垒机并实现无公网ip环境远程访问

文章目录 前言1. 安装Jump server2. 本地访问jump server3. 安装 cpolar内网穿透软件4. 配置Jump server公网访问地址5. 公网远程访问Jump server6. 固定Jump server公网地址 前言 JumpServer 是广受欢迎的开源堡垒机&#xff0c;是符合 4A 规范的专业运维安全审计系统。JumpS…

leetcode刷题-110 平衡二叉树的判断(递归实现)

题目描述 解题思路 首先解释一下&#xff0c;为什么会做到这个题目&#xff0c;因为博主在按顺序做题的过程中&#xff0c;碰到了一个不会做的题目&#xff08;递归类型&#xff09;&#xff0c;就想着看看题解&#xff0c;发现了一个大佬的文章&#xff0c;就是专门讲的递归&…

树及二叉树

文章目录 树定义相关概念树的表示 二叉树定义特殊的二叉树二叉树的性质 树 定义 树是一种非线性的数据结构&#xff0c;它由n个有限结点组成的有层次关系的集合。 如图所示&#xff0c;树的第一个结点是一个特殊的结点&#xff0c;它没有前驱结点&#xff0c;称为根结点。此外…

Linux系统部署Discuz论坛并发布至公网随时随地可远程访问

目录 ​编辑 前言 1.安装基础环境 2.一键部署Discuz 3.安装cpolar工具 4.配置域名访问Discuz 5.固定域名公网地址 6.配置Discuz论坛 结语 作者简介&#xff1a; 懒大王敲代码&#xff0c;计算机专业应届生 今天给大家聊聊Linux系统部署Discuz论坛并发布至公网随时随地…

06、MongoDB -- MongoDB 基本用法(删除文档、查询文档、查询运算符)

目录 MongoDB 基本用法演示前提&#xff1a;登录单机模式的 mongodb 服务器命令登录【admin】数据库的 mongodb 客户端命令登录【test】数据库的 mongodb 客户端命令 删除文档语法格式两个变体版本&#xff1a;1、remove&#xff1a;根据【name】字段删除一条文档2、deleteOne&…

Three.js--》探寻Cannon.js构建震撼的3D物理交互体验(一)

我们用three.js可以绘制出各种酷炫的画面&#xff0c;但是当我们想要一个更加真实的物理效果的话&#xff0c;这个时候我们就需要一个物理的库&#xff0c;接下来我们就讲解一下今天要学习的canon&#xff0c;它可以给我们提供一个更加真实的物理效果&#xff0c;像物体的张力、…

linux安装mysql5.7

linux安装mysql5.7 一、下载mysql5.7二、解压包介绍三、上传包到linux四、卸载mariadb五、安装mysql六、修改权限七、启动mysql八、使用过navicat创作不易&#xff0c;笔记不易&#xff0c;如觉不错&#xff0c;请三连&#xff0c;谢谢~~ 一、下载mysql5.7 去mysql官方下载&am…

kafka启动命令、查看topic命令、查看消息内容命令

kafka启动命令 cd /opt/kafka/kafka_2.12-3.5.1/bin ./kafka-server-start.sh ../config/server.properties Windows环境下用kafka Tool 连不上虚拟机的broker报了unable to connect broker 0&#xff0c; 但是zookeeper可以连接上 server.properties的listeners改为listene…

数据结构从入门到精通——链表

链表 前言一、链表1.1 链表的概念及结构1.2 链表的分类1.3 链表的实现1.4 链表面试题1.5 双向链表的实现 二、顺序表和链表的区别三、单项链表实现具体代码text.htext.cmain.c单链表的打印空间的开辟链表的头插、尾插链表的头删、尾删链表中元素的查找链表在指定位置之前、之后…

力扣 分割回文串

输出的是不同的分割方案 class Solution { public:vector<vector<bool>>flag;vector<string>ans;vector<vector<string>>nums;void dfs(string &s,int i){int ns.size();if(in){i表示s长度&#xff0c;等于即全部分割完毕nums.push_back(ans…

大文件传输之udp收发包错误如何解决

数据传输的速度和稳定性对于企业运营至关重要。UDP&#xff08;用户数据报协议&#xff09;作为一种无连接的网络协议&#xff0c;以其高效的数据传输能力在实时应用中得到了广泛应用。然而&#xff0c;UDP的不可靠性也带来了收发包错误的问题&#xff0c;这在需要高数据完整性…

【MGR】MySQL Group Replication快速开始

目录 17.2 Getting Started 17.2.1 Deploying Group Replication in Single-Primary Mode 17.2.1.1 Deploying Instances for Group Replication 17.2.1.2 Configuring an Instance for Group Replication Storage Engines Replication Framework Group Replication Sett…

【好书推荐-第七期】《RTC程序设计:实时音视频权威指南》(音视频开发必看!)

&#x1f60e; 作者介绍&#xff1a;我是程序员洲洲&#xff0c;一个热爱写作的非著名程序员。CSDN全栈优质领域创作者、华为云博客社区云享专家、阿里云博客社区专家博主、前后端开发、人工智能研究生。公粽号&#xff1a;洲与AI。 &#x1f388; 本文专栏&#xff1a;本文收录…

物联网边缘计算云边协同

文章目录 一、物联网云边协同1.IoT云边协同设计2.物联网平台设计3.物联网平台实现 二、部署环境1.节点配置2.版本信息 三、IoT云边协同部署1.部署Kubernetes集群2.部署KubeEdge3.部署ThingsBoard集群4.部署Node-RED边缘网关4.1.边缘网关功能4.2.部署EMQX4.2.部署Node-RED 5.配置…

【sgCollapseBtn】自定义组件:底部折叠/展开按钮

特性&#xff1a; 支持自定义折叠状态支持自定义标签名称 sgCollapseBtn源码 <template><div :class"$options.name" click"show !show" :placement"placement"><div class"collapse-btns"><div class"c…

YOLOv9独家原创改进|加入幽灵卷积Ghost Convolution模块,轻量化!

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;主力高效涨点&#xff01;&#xff01;&#xff01; 一、论文摘要 由于内存和计算资源有限&#xff0c;在嵌入式设备上部署卷积神经网络是困难的。特征图中的冗余是那些成功的细胞神经网络的一个重要特征…

Linux基础命令[10]-cmp

文章目录 1. cmp 命令说明2. cmp 命令语法3. cmp 命令示例3.1 不加参数3.2 -b&#xff08;显示不同的字节&#xff09;3.3 -i&#xff08;跳过字节&#xff09;3.4 -l&#xff08;显示所有不同&#xff09;3.5 -n&#xff08;比较n个字节&#xff09;3.6 -s&#xff08;不显示信…

深圳牵头打造鸿蒙原生应用软件生态 | 百能云芯

深圳市工业和信息化局、深圳市政务服务和数据管理局于3月3日联合印发了《深圳市支持开源鸿蒙原生应用发展2024年行动计划》。这一计划旨在通过政策引导、市场推动、社会协同的方式&#xff0c;将深圳打造成一个鸿蒙原生应用软件生态的中心&#xff0c;推动鸿蒙系统在当地的发展…

Spring Boot2.2.4版本启动项目时,访问登录接口显示页面不存在

问题触发场景&#xff1a;IDEA 2023.3.4 SpringBoot 2.2.4 上面4张图片分别是项目结构、Spring Boot启动配置、SpringMVC配置和页面展示在项目中存放的位置&#xff0c;表面上看上去没有太大问题&#xff0c;项目应该会达到预期结果&#xff0c;但是bug总是在不经意间出现&…