代码随想录 刷题记录-19 动态规划(3)完全背包理论、习题

news2024/12/24 1:00:09

一、完全背包理论

52. 携带研究材料

有N种物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。

完全背包和01背包问题唯一不同的地方就是,每种物品有无限件

首先从二维dp上来讲:

1.dp数组及下标含义:

        dp[i][j] : 考虑前 i种物品,背包容量为j时所能装入的最大价值

2.递推公式

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

注意这里与01背包不同的是,每种物品可以取无限多次,公式右边的第一项代表不考虑物品i的情况,公式右边第二项代表考虑物品i 1个或多个的情况,由于是依赖于本行的值,当第一次进入时如果选择右边就是拿了1个,第二次在j增加后还选择右边,依赖的左边的本行值已经更新过了,所以再次选择右边就包括了多个的情况,即右边这一项是物品无限件的体现,它的核心在于使用本行的值而不是上一行的值进行更新。

3.初始化

        根据递推公式,计算时依赖于上一行和本行左边,对物品为0的第一行进行初始化,当 j >= weight[0] 时, dp[0][j] = dp[0][j-weight[0]] +value[0]

4.遍历顺序

        先物品后容量 或者 先容量或物品都可以,这里先物品后容量

        正序遍历,因为需要用到本行左边已经计算的值。

5.dp模拟

代码如下:

import java.util.Scanner;

public class Main{
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int capacity = scanner.nextInt();
        int[] weight = new int[n];
        int[] value = new int[n];
        for(int i = 0; i < n ; i++){
            weight[i] = scanner.nextInt();
            value[i] = scanner.nextInt();
        }
        int[][] dp = new int[n][capacity+1];
        
        for(int j = weight[0]; j <= capacity ; j++){
            dp[0][j] = dp[0][j-weight[0]] + value[0];
        }
        
        for(int i = 1 ; i < n ; i++){
            for(int j = 0; j <= capacity ; j++){
                if(j < weight[i]) dp[i][j] = dp[i-1][j];
                else dp[i][j] = Math.max(dp[i-1][j],dp[i][j-weight[i]]+value[i]);
            }
        }
        System.out.println(dp[n-1][capacity]);
    }
}

接下来考虑使用滚动数组的情况:

01背包和完全背包唯一不同就是体现在遍历顺序上,这里直接针对遍历顺序进行分析。

首先再回顾一下01背包的核心代码

for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

我们知道01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次。

而完全背包的物品是可以添加多次的,所以要从小到大去遍历,即:

// 先遍历物品,再遍历背包
for(int i = 0; i < weight.size(); i++) { // 遍历物品
    for(int j = weight[i]; j <= bagWeight ; j++) { // 遍历背包容量
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

    }
}

即完全背包恰好利用了01背包如果正序遍历则会覆盖的特点,因为完全背包需要的数据是本行的左边,这要求左边必须计算过,所以要中序遍历。

也是因为完全背包对背包容量是正序遍历的,这也就导致了他的物品循环和背包容量循环的嵌套顺序是可以互换的(在二维里相当于从左到右一列一列的,所需要的数据在本行左边和上边,已经计算过了)

代码如下:

import java.util.Scanner;

public class Main{
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int capacity = scanner.nextInt();
        int[] weight = new int[n];
        int[] value = new int[n];
        for(int i = 0; i < n ; i++){
            weight[i] = scanner.nextInt();
            value[i] = scanner.nextInt();
        }
        int[] dp = new int[capacity+1];
        

        for(int i = 0 ; i < n ; i++){
            for(int j = weight[i]; j <= capacity ; j++){
                dp[j] = Math.max(dp[j],dp[j-weight[i]]+value[i]);
            }
        }
        System.out.println(dp[capacity]);
    }
}

二、习题

1.518.零钱兑换II

回溯法,但是会超时:

class Solution {
    int[] coins;
    int sum = 0;
    int amount;
    int result = 0;
    public int change(int amount, int[] coins) {
        this.coins = coins;
        this.amount = amount;
        dfs(0);
        return result;
    }

    public void dfs(int startIndex){
        if(sum == amount){
            result++;
            return;
        }
        if(sum > amount){
            return ;
        }
        for(int i = startIndex ; i < coins.length ; i++){
            sum += coins[i];
            dfs(i);
            sum -= coins[i];
        }
    }
}

考虑动态规划:

每个钱币能选择的数量不限,属于完全背包问题。

本题和纯完全背包不一样,纯完全背包是凑成背包最大价值是多少,而本题是要求凑成总金额的物品组合个数。

注意这里属于组合数,而不是排列数。

动规五步曲:

1.dp数组及下标含义

dp[j] : 当前遍历的前i种钱币中,容量为j下有 dp[j] 种方案凑满。

2.递推公式

dp[j]  += dp[j - coins[i]]

这个递推公式大家应该不陌生了,我在讲解01背包题目的时候在这篇494. 目标和 中就讲解了,求装满背包有几种方法,公式都是:dp[j] += dp[j - nums[i]];

3.初始化

dp[0] = 1;

首先dp[0]一定要为1,dp[0] = 1是 递归公式的基础。如果dp[0] = 0 的话,后面所有推导出来的值都是0了。

那么 dp[0] = 1 有没有含义,其实既可以说 凑成总金额0的货币组合数为1,也可以说 凑成总金额0的货币组合数为0,好像都没有毛病。

但题目描述中,也没明确说 amount = 0 的情况,结果应该是多少。

这里我认为题目描述还是要说明一下,因为后台测试数据是默认,amount = 0 的情况,组合数为1的。

下标非0的dp[j]初始化为0,这样累计加dp[j - coins[i]]的时候才不会影响真正的dp[j]

dp[0]=1还说明了一种情况:如果正好选了coins[i]后,也就是j-coins[i] == 0的情况表示这个硬币刚好能选,此时dp[0]为1表示只选coins[i]存在这样的一种选法。

4.遍历顺序

外层是钱币种类。

内层是容量。

因为可选是无限次,所以正序。

在滚动数组下,必须外层是钱币种类,内层是容量,这样求出来的是组合数,反之求出的是排列数:

or

or

在二维dp数组下,遍历的内外层顺序可以交换。

5.dp模拟

代码如下:

public int change(int amount, int[] coins) {
        int[] dp = new int[amount+1];
        dp[0] = 1;
        for(int i = 0 ; i < coins.length ; i++){
            for(int j = coins[i] ; j <= amount ; j++){
                dp[j] += dp[j - coins[i]];
            }
        }
        return dp[amount];
    }

2.377. 组合总和 Ⅳ

本题是完全背包+排列数量和 问题,本题题目要求组合数,但是指出顺序不同的是不同组合,其实就是求排列数。

在学习回溯算法专题的时候,一定做过这两道题目回溯算法:39.组合总和 和回溯算法:40.组合总和II会感觉这两题和本题很像。

但其本质是本题求的是排列总和,而且仅仅是求排列总和的个数,并不是把所有的排列都列出来。

如果本题要把排列都列出来的话,只能使用回溯算法爆搜

动规五部曲分析如下:

1.dp数组及下标含义:

dp[j] : 凑成正整数为j的排列个数 为 dp[j]

2.确定递推公式

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

3.初始化

dp[0] = 1

4.遍历顺序

因为是求排列数,所以先遍历容量,再遍历物品,这样排列不同的算作不同。

由于每个物品可以用无限次,对容量正序遍历。

5.dp模拟

3.57. 爬楼梯

这其实是一个完全背包问题。

1阶,2阶,.... m阶就是物品,楼顶就是背包。

每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。

问跳到楼顶有几种方法其实就是问装满背包有几种方法。

动规五部曲:

1.dp数组及下标含义

dp[j] : 到第j层有 dp[j] 种方法

2.递推公式

dp[j] += dp[j-i]   i从1到m进行遍历

3.初始化

dp[0] = 1;

4.遍历顺序

先跨1层后跨2层 与 先跨2层后跨1层 是不一样的方案,所以求的是排列数,外层容量,内层物品。

完全背包,正序遍历。

5.dp模拟

3.322. 零钱兑换

完全背包问题

动规五部曲:

1.dp数组及下标含义:

dp[j] :凑足金额j所需要的最少的硬币个数为 dp[j] 个

2.递推公式

dp[j] = Math.min(dp[j] , dp[j-i] +1)

3.初始化

凑足金额0所需要的硬币个数为0,所以dp[0] = 0。

考虑到递推公式取最小值,初始化的值必须不影响取最小的操作,所以除了dp[0]以外初始化成Interger.MAX_VALUE,否则就有可能在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。

4.遍历顺序

本题并不强调集合是组合还是排列。

如果求组合数就是外层for循环遍历物品,内层for遍历背包

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

在动态规划专题我们讲过了求组合数是动态规划:518.零钱兑换II (opens new window),求排列数是动态规划:377. 组合总和 Ⅳ (opens new window)。

所以本题的两个for循环的关系是:外层for循环遍历物品,内层for遍历背包或者外层for遍历背包,内层for循环遍历物品都是可以的!

本题是背包问题,背包容量遍历是正序。

5.dp模拟

以输入:coins = [1, 2, 5], amount = 5为例

322.零钱兑换

dp[amount]为最终结果。

代码如下:

(注意在递推时要排除初始值的影响)

dp[j] = Math.mim(dp[j], dp[j-coins[i] +1 ) ,如果不判断是否是Integer.MAX_VALUE的话,dp[j-coins[i]] +1可能移除,为负值,影响递推结果。

for(int i = 0 ; i < coins.length ; i++){
            for(int j = coins[i] ; j <= amount ; j++){
                if(dp[j - coins[i]]!=Integer.MAX_VALUE)
                dp[j] = Math.min(dp[j],dp[j-coins[i]]+1);
            }
        }
class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount+1];
        dp[0] = 0;
        for(int i = 1 ; i < dp.length ; i++){
            dp[i] = Integer.MAX_VALUE;
        }
        for(int i = 0 ; i < coins.length ; i++){
            for(int j = coins[i] ; j <= amount ; j++){
                if(dp[j - coins[i]]!=Integer.MAX_VALUE)
                dp[j] = Math.min(dp[j],dp[j-coins[i]]+1);
            }
        }
        return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount] ;
    }
}

4.279.完全平方数

完全背包问题

1.dp数组及下标含义

dp[j]:组成数j最少需要 dp[j]个完全平方数

2.递推公式

dp[j] = Math.min(dp[j] , dp[j - nums[i]] + 1)

3.初始化

dp[0] = 0;

dp的其他元素初始化成Integer.MAX_VALUE

4.遍历顺序

外层物品,内层背包容量,可以交换。

完全背包,容量正序遍历。

5.dp模拟

已输入n为5例,dp状态图如下:

279.完全平方数

代码如下:

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

总结

本周的主题其实就是背包问题中的遍历顺序!

我这里做一下总结:

求组合数:动态规划:518.零钱兑换II (opens new window)求排列数:动态规划:377. 组合总和 Ⅳ (opens new window)、动态规划:70. 爬楼梯进阶版(完全背包) (opens new window)求最小数:动态规划:322. 零钱兑换 (opens new window)、动态规划:279.完全平方数(opens new window)

此时我们就已经把完全背包的遍历顺序研究的透透的了!

5.139.单词拆分

可以使用回溯法,会时间超限

考虑动态规划

1.dp数组及下标含义:

dp[i] : dp[i] 为true 表示前i个字符组成的字符串可以分割成字典中字符串。

2.递推公式

如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )。

所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。

for(int j = 0; j < i ; j++){
    if(dp[j] && check(j,i)) dp[i] = true;
}

3.初始化

从递推公式看出,dp[i] 依赖于 在dp数组中其前面的元素。

故dp[0] 必须为 true

dp[0] = true;

dp[0]有没有意义呢?

dp[0]表示如果字符串为空的话,说明出现在字典里。

但题目中说了“给定一个非空字符串 s” 所以测试数据中不会出现i为0的情况,那么dp[0]初始为true完全就是为了推导公式。

下标非0的dp[i]初始化为false,只要没有被覆盖说明都是不可拆分为一个或多个在字典中出现的单词。

4.遍历顺序

字典中的字符串可以使用多次,属于完全背包,正序遍历。

本题对顺序有要求,属于排列,需要外层遍历物品,内层遍历背包。

5.dp模拟

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        Set<String> set = new HashSet<>(wordDict);
        boolean[] dp = new boolean[s.length()+1];
        dp[0] = true;
        for(int i = 1; i <= s.length() ; i++){
            for(int j = 0;  j < i ; j++){
                if(dp[j] && set.contains(s.substring(j,i))) dp[i] = true;
            }
        }
        return dp[s.length()];

    }
}

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

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

相关文章

(二) 初入MySQL 【数据库管理】

一、SQL语言分类 DDL&#xff1a;数据定义语言&#xff0c;用于创建数据库对象&#xff0c;如库、表、索引等 DML&#xff1a;数据操纵语言&#xff0c;用于对表中的数据进行管理,用来插入、删除和修改数据库中的数据 DQL&#xff1a;数据查询语言&#xff0c;用于从数据表中查…

Mybatis:基础巩固-DML

目录 一、概述二、新增三、修改四、删除 一、概述 DML语句主要用来完成对数据的增删改的操作。可以分为以下几类&#xff1a; 新增&#xff08;INSERT&#xff09;修改&#xff08;UPDATE&#xff09;删除&#xff08;DELETE&#xff09; 二、新增 INSERT INTO tb_user(name, …

【MySQL 14】用户管理

文章目录 &#x1f308; 一、用户管理⭐ 1. 用户信息⭐ 2. 创建用户⭐ 3. 修改密码⭐ 4. 删除用户 &#x1f308;二、数据库的权限⭐ 1. 权限列表⭐ 2. 查看权限⭐ 3. 授予权限⭐ 4. 回收权限 &#x1f308; 一、用户管理 MySQL 也会区分普通用户和超级用户。如果只是访问某一…

centos基础设置

1.设置网络 # 配置网络 vi /etc/sysconfig/network-scripts/ifcfg-ens33 #将里面的onboard设置为yes 2.修改计算机名 使用命令行&#xff1a; 打开终端。 使用 hostnamectl 命令可以直接修改主机名。例如&#xff0c;如果您想将主机名更改为 mynewhostname&#xff0c;您可以使…

selenium(一)基于java、元素定位

Selenium自动化 Selenium是一个用于Web应用程序的自动化测试工具。它直接运行在浏览器中&#xff0c;可以模拟用户在浏览器上面的行为操作。 chrome下载 https://www.google.com/chrome/ 下载的结果是 “ChromeSetup.exe”&#xff0c;双击该文件&#xff0c;安装程序会自…

selenium(二)基于java、元素操控、Frame切换、元素等待

元素操控 选择到元素之后&#xff0c;我们的代码会返回元素对应的 WebElement对象&#xff0c;通过这个对象&#xff0c;我们就可以 操控 元素 输入sendKeys 项目实战&#xff1a;在网银环境下载流水[交易明细]的时候&#xff0c;需要指定查询日期&#xff0c;有些银行是可以…

一个简单的CRM客户信息管理系统,提供客户,线索,公海,联系人,跟进信息和数据统计功能(附源码)

前言 在当今快速发展的商业环境中&#xff0c;企业管理面临着种种挑战&#xff0c;尤其是如何有效管理和维护客户关系成为了一个关键问题。现有的一些处理方案往往存在功能分散、操作复杂、数据孤岛等痛点&#xff0c;这不仅影响了工作效率&#xff0c;也难以满足企业对客户关…

仿华为车机功能之--修改Launcher3,实现横向滑动桌面空白处切换壁纸

本功能基于Android13 Launcher3 需求:模仿华为问界车机,实现横向滑动桌面空白处,切换壁纸功能(本质只是切换背景,没有切换壁纸)。 实现效果: 实现思路: 第一步首先得增加手势识别 第二步切换底图,不切换壁纸是因为切换壁纸动作太大,需要调用到WallpaperManager,耗…

【赵渝强老师】构建Docker Swarm集群

在了解了Docker Swarm的体系架构以后&#xff0c;基于已经部署好三台虚拟主机便可以非常容易地构建Swarm集群。下表列出了这三台虚拟机的相关信息。其中&#xff1a;master主机将作为Swarm Manager运行&#xff1b;而node1和node2将作为Swarm Node运行。 视频讲解如下&#xf…

nacos 动态读取nacos配置中心项目配置

实现了项目稳定运行情况下不需要在项目中改配置&#xff0c;直接在nacos中修改更方便。 pom文件&#xff1a; <!--读取bootstrap文件--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap…

大数据测试怎么做,数据应用测试、数据平台测试、数据仓库测试

本期内容由中通科技高级质量工程师龙渊在公益讲座中分享&#xff0c;他从大数据测试整体介绍、数据应用测试、数据平台测试以及数据仓库测试等方面&#xff0c;与大家共同探讨了大数据测试的方法实施与落地。 以下是讲座正文&#xff1a; 今天我们分享的内容主要从大数据简介…

阿里云Ubuntu系统安装/简单使用Kafka

一、安装kafka 1.下载安装包 1.1下载地址 https://kafka.apache.org/downloads 注意&#xff1a; 版本可以随意选择&#xff0c;我们选择版本为2.4.1 2.压缩文件上传/解压 2.1上传 2.2解压文件 #解压文件指令 tar -zxvf kafka_2.12-2.4.1.tgz -C /export/server/ #创建软…

py_gif制作

from PIL import Image import moviepy.editor as mp import os# 1. 定义函数 resize_frame&#xff0c;用于调整视频帧的大小 def resize_frame(frame, max_size240):img Image.fromarray(frame)img img.resize((max_size, max_size), Image.Resampling.LANCZOS) # 使用 LA…

【Java】—— Java面向对象基础:Java中如何创建和使用三角形类(TriAngle)来计算面积

目录 第一步&#xff1a;定义TriAngle类 第二步&#xff1a;编写TriAngleTest测试类 运行程序 总结 在Java编程中&#xff0c;面向对象的编程思想是一种非常强大的工具&#xff0c;它允许我们将现实世界中的实体&#xff08;如三角形&#xff09;抽象成程序中的类&#xff…

c++关于字符串的练习

提示并输入一个字符串&#xff0c;统计该字符串中字母个数、数字个数、空格个数、其他字符的个数 #include <iostream> #include<string> using namespace std;int main() {string s1;int letter0,digit0,space0,other0;cout<<"请输入一个字符串:"…

redis学习笔记 ——redis中的四大特殊数据结构

一.前言 在之前的学习中&#xff0c;我们已经介绍了Redis中常见的五种基本的数据结构&#xff0c;而今天我们就要开始介绍Redis的四种特殊的数据结构&#xff0c;它们分别是bitmap(位图)&#xff0c; HyperLogLog(基数统计),Geospatial(地理信息),Stream。 二.位图(Bitmap) …

springboot+vue+mybatis计算机毕业设计电子产品交易系统+PPT+论文+讲解+售后

系统根据现有的管理模块进行开发和扩展&#xff0c;采用面向对象的开发的思想和结构化的开发方法对电子产品交易管理的现状进行系统调查。采用结构化的分析设计&#xff0c;该方法要求结合一定的图表&#xff0c;在模块化的基础上进行系统的开发工作。在设计中采用“自下而上”…

并发高负载场景下的负载均衡优化方案

并发高负载场景下&#xff0c;负载均衡优化技术 负载均衡是一种计算机技术&#xff0c;主要用于在多个计算机(如计算机集群)、网络连接、CPU、硬盘驱动器或其他资源中分配工作负载。 其主要目标是优化资源使用、最大化吞吐率、最小化响应时间&#xff0c;同时避免任何一个资源的…

跨境电商避坑指南:如何在亚马逊和速卖通安全进行测评补单

大家好&#xff0c;近期&#xff0c;不少从事跨境电商的朋友纷纷向我求助&#xff0c;他们在执行测评补单时频繁遭遇支付难题&#xff0c;如支付失败、支付成功后订单被取消&#xff0c;更有甚者&#xff0c;账号遭遇封禁。许多朋友误以为这仅仅是支付卡的问题&#xff0c;但实…

【生信分析常用软件】plink常用功能之翻转正负链(--flip)

官网:PLINK 1.9 plink --bfile plink --flip flip.txt --make-bed --out test 当不同数据合并时&#xff0c;如果一个数据使用正链&#xff0c;另外一个数据使用反链&#xff0c;就会导致合并数据出现问题&#xff0c;报错。 这种时候方案一&#xff1a;舍弃所有不能合并位点&…