递归到动态规划:空间压缩技巧-纸币问题的有限张数

news2024/11/24 3:09:29
这个题是我们纸币问题的第三题

题目大意:

arr是货币数组,其中的值都是正数。再给定一个正数aim

每个值都认为是一张货币,

认为值相同的货币没有任何不同,

返回组成aim的方法数

例如:arr = {1,2,1,1,2,1,2}aim = 4

方法:1+1+1+11+1+22+2

一共就3种方法,所以返回3

这个题跟我们上一道题的区别是每种纸币在数组中出现了多少次就是多少张,你只能用这么多张(假设张数是N)进行0~N的尝试,尝试过程不能超过剩余金额,所以限制条件有两个:

1. 张数只能从0~N 

2.张数*当前面值不能超过剩余金额

而我们之前的题目是每个面值的有无数张,限制条件只有一个:

张数*当前面值不能超过剩余金额,不限制张数

一步步改的代码如下,注释特别详细

package dataStructure.recurrence.practice;

import java.util.HashMap;

/**
 * arr是货币数组,其中的值都是正数。再给定一个正数aim。
 * 每个值都认为是一张货币,
 * 认为值相同的货币没有任何不同,
 * 返回组成aim的方法数
 * 例如:arr = {1,2,1,1,2,1,2},aim = 4
 * 方法:1+1+1+1、1+1+2、2+2
 * 一共就3种方法,所以返回3
 */
public class CoinsWaySameValueSamePaper {
    //辅助的Info类,用来保存张数和面值
    public static class Info {
        //张数和面值数组,二者长度相等
        int[] nums;
        int[] values;

        public Info(int[] nums, int[] values) {
            this.nums = nums;
            this.values = values;
        }
    }
    public static int coinsWay(int[] arr, int aim) {
        Info info = generateInfo(arr);
        return process1(info.nums, info.values, 0, aim);
    }

    /**
     * 根据暴力递归改的动态规划-普通动态规划
     * @param arr
     * @param aim
     * @return
     */
    public static int coinsWayDp(int[] arr, int aim) {
        Info info = generateInfo(arr);
        int[] nums = info.nums;
        int[] values = info.values;
        int N = nums.length;
        //上面的部分和原来一样
        //dp是二维的,行代表index, 从0 到 N, 列代表剩余钱数,从0到aim
        int[][] dp = new int[N + 1][aim + 1];
        //下标为N的行(也就是最后一行)只有rest=0的时候是1,其他都是0
        dp[N][0] = 1;
        for(int index = N - 1; index >= 0; index --) {
            //从倒数第一行开始依次往上天,列的顺序从左到右或者从右到左都行
            for(int rest = 0; rest <= aim; rest ++) {
                int ways = 0;
                //递归中的枚举过程,i * values[index] <= rest保证rest - i * values[index]不会越界
                for(int i = 0; i * values[index] <= rest && i <= nums[index]; i++) {
                    //System.out.println("rest=" + rest + "value[index]=" + values[index] + " i=" + i);
                    ways += dp[index + 1][rest - i * values[index]];
                }
                dp[index][rest] = ways;
            }
        }
        return dp[0][aim];
    }

    /**
     * 动态规划的本题最好版本-省去枚举过程
     * @param arr
     * @param aim
     * @return
     */
    public static int coinsWayDpBest(int[] arr, int aim) {
        Info info = generateInfo(arr);
        int[] nums = info.nums;
        int[] values = info.values;
        int N = nums.length;
        int[][] dp = new int[N + 1][aim + 1];
        //下标为N的行(也就是最后一行)只有rest=0的时候是1,其他都是0
        dp[N][0] = 1;
        //以上过程和普通动态规划版本完全一致
        for(int index = N - 1; index >= 0; index --) {
            for(int rest = 0; rest <= aim; rest ++) {
                //dp[index][rest] = dp[index+1][rest] + dp[index+1][rest - values[index]] + ...
                // 一直加到rest - (nums[index] + 1) * values[index]或者nums[index]用完
                //dp[index+1][rest]是使用0张,肯定不会超过两个限制,可以用来赋初始值
                dp[index][rest] = dp[index+1][rest];
                //如果rest小于当前的的面值,下面就没有必要进行遍历了(这个也是为了保证同行使用一张那个位置不越界)
                if(rest - values[index]>=0) {
                    //一个位置先用它下面同列+同行列左移values[index]列
                    dp[index][rest] += dp[index][rest -  values[index]];
                    //这个结果可能算重复了一个位置,这个位置就是dp[index][rest -  values[index]]的计算过程中下一行最左边要计算的位置
                    //如果rest - (nums[index] + 1) * values[index] >= 0才需要减,如果满足这个条件,说明:
                    //dp[index][rest] = dp[index+1][rest] + dp[index+1][rest - values[index]] + ...+dp[index+1][rest - values[index]*nums[index]]
                    //dp[index][rest - values[index]] = dp[index+1][rest - values[index]] + ...+dp[index+1][rest - values[index]*nums[index]] + dp[index+1][rest - values[index]*(nums[index]+1)]
                    //使用dp[index][rest] += dp[index][rest -  values[index]];就多算了一个dp[index + 1][rest - (nums[index] + 1) * values[index]],这里要减去
                    if(rest - (nums[index] + 1) * values[index] >= 0) {
                        dp[index][rest] -= dp[index + 1][rest - (nums[index] + 1) * values[index]];
                    }
                }
            }
        }
        return dp[0][aim];
    }

    /**
     * 递归方法使用index下标开始的硬币拼成rest
     * @param nums 原始的数量数组,nums[i]代表i位置的货币(面值为values[i])有几张
     * @param values 原始的面值数组,values[i]表示i位置的货币是多大面值
     * @param index 当前要尝试的位置的下标
     * @param rest 目前剩余的金额
     * @return
     */
    public static int process1(int[] nums, int[] values, int index, int rest) {
        if(index == nums.length) {
            return rest == 0? 1 : 0;
        }
        int ways = 0;
        //两种限制:1. 张数只能从0~nums[index]
        //2.张数*当前面值不能超过剩余金额
        for(int i = 0; i * values[index] <= rest && i <= nums[index]; i++) {
            //System.out.println("rest=" + rest + "value[index]=" + values[index] + " i=" + i);
            //所有可能性的总和就是我们要的结果
            ways += process1(nums, values, index + 1, rest - i * values[index]);
        }
        return ways;
    }

    /**
     * 根据原始数组获取对应的Info信息,主要是获取那两个数组:张数和面值的信息
     * @param arr
     * @return
     */
    private static Info generateInfo(int[] arr) {
        //使用hashmap进行去重和统计数量
        HashMap<Integer, Integer> map = new HashMap<>();
        //遍历原数组每个位置
        for(int i = 0; i < arr.length; i++) {
            //统计每种金额数量
            if(map.containsKey(arr[i])) {
                map.put(arr[i],map.get(arr[i]) + 1);
            } else {
                map.put(arr[i], 1);
            }
        }
        int[] nums = new int[map.size()];
        int[] values = new int[map.size()];
        int index = 0;
        //key是面值,value是张数,依次填充两个数组
        for (Integer key : map.keySet()) {
            //System.out.println(key + " " + map.get(key));
            values[index] = key;
            nums[index ++] = map.get(key);
        }

        return new Info(nums, values);
    }

    public static void main(String[] args) {
        int[] arr = {1,2,1,1,2,1,2};
        int aim = 4;
        int coinsWay = coinsWay(arr, aim);
        System.out.println(coinsWay);

        int coinsWayDp = coinsWayDp(arr, aim);
        System.out.println(coinsWayDp);
        int coinsWayDpBest = coinsWayDpBest(arr, aim);
        System.out.println(coinsWayDpBest);
    }
}

过程分析图:

 

不明白有问题可以私信我,保证你懂

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

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

相关文章

【C】模拟实现atoi,atof函数

目录 atoi函数 atof函数 模拟实现atoi&#xff0c;atof函数 1、atoi模拟实现 2、atof模拟实现 3、测试案例代码 atoi函数 atoi函数是将字符串转换成整数 函数头文件&#xff1a;#include <stdlib.h> 函数原型&#xff1a;int atoi(const char *str); 参数&…

利用结构相似性做单细胞多模态分析

多模态单细胞测序技术从多层基因组数据中提供了丰富的细胞异质性信息。然而&#xff0c;在没有正确消除模态偏差的情况下去分析联合空间&#xff0c;往往会得到比单模态分析更差的聚类结果。如何有效利用多组学额外信息来描绘细胞状态并识别有意义的信号仍然是一个重大的挑战。…

华为 VOS 移植到 TDA4VM/VH 芯片的 TI RTOS SDK 时的 bug 修复笔记

请从官网下载 TD4VM 技术参考手册&#xff0c;地址如下&#xff1a; TDA4VM 技术参考手册地址 VOS 作为静态库移植到TDA4VM/VH 芯片的 TI RTOS SDK 中 VOS 移植到 mcusw/mcal_drv/mcal/vos&#xff0c;如下&#xff1a; vos 测试应用 在 mcusw/mcuss_demos/vos_test_app …

Shell脚本之正则表达式

目录 一、正则表达式的介绍 1&#xff09;正则表达式的组成 2&#xff09;正则表达式和通配符的区别 二、基础正则表达式 1&#xff09;转义字符的运用 将特殊含义的字符转换为普通字符的含义 将普通字符转换为特殊作用的字符 2&#xff09;基础正则表达式实际应用 查…

C++ | 结构体及大小计算

C结构体及大小计算 文章目录 C结构体及大小计算struct 和 class 区别字节对齐默认对齐方式 位域使用#pragma pack(n)结构体中有结构体Reference struct 和 class 区别 结构体&#xff08;struct&#xff09;和类&#xff08;class&#xff09;有点像&#xff0c;均是定义一个数…

Activi7工作流经典实战(附:常用流程流转代码片段)

一、Activiti7介绍 Activiti正是目前使用最为广泛的开源工作流引擎。Activiti的官网地址是 https:// www.activiti.org 历经6.x和5.x两个大的版本。 1. Activiti工作流引擎 他可以将业务系统中复杂的业务流程抽取出来&#xff0c;使用专门的建模语言BPMN2.0进行定义。业务流…

彻底搞清楚Handler,再也不怕面试官

Handler Handler可以说是Android框架里面很精髓的一部分了&#xff0c;面试必问&#xff0c;用的也最多 Handler是什么&#xff1f; 提到Handler大家一定不陌生&#xff0c;我们经常用它来切换线程&#xff0c;或者是说做一些延时任务等等。最常用的地方可能就是在网络请求中…

Flask全栈解决小问题系列(1)搭建一个bootstrap开发框架

时间不多,闲话少说,实践出真知! 1.目的:为实现FlaskBootStrap开发效果,搞个开发测试项目 2.搭建项目 1)建个test-bootstrap项目,项目目录结构如下: 2)appstart.py内容如下: import json from flask import Flask,redirect,render_templateapp Flask("__main__") …

00后太卷了上班还没3年,跳到我们公司起薪18k....

都说00后已经躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。前段时间我们部门就来了个00后&#xff0c;工作都还没三年&#xff0c;跳到我们公司起薪18K&#xff0c;都快接近我了。 后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。最近和他…

Yolov5/Yolov7改进:小目标到大目标一网打尽,轻骨干重Neck的轻量级目标检测器GiraffeDet

1.GiraffeDet介绍 论文:https://arxiv.org/abs/2202.04256 🏆🏆🏆🏆🏆🏆Yolov5/Yolov7魔术师🏆🏆🏆🏆🏆🏆 ✨✨✨魔改网络、复现前沿论文,组合优化创新 🚀🚀🚀小目标、遮挡物、难样本性能提升 🍉🍉🍉定期更新不同数据集涨点情况 本文是…

gitlab上传大文件限制问题解决

gitlab上传大文件限制问题解决 前景提要&#xff1a; 今天收到同事反馈遇到gitlab 上传大文件时候报如下错误 error: RPC failed; result22, HTTP code 413 fatal: The remote end hung up unexpectedly fatal: The remote end hung up unexpectedly从报错来看是因为文件大…

什么样的冷链保温箱,既环保又实用?

冷链物流运输已经应用在了很多行业中&#xff0c;作为冷链物流运输中的重要设备——冷链保温箱&#xff0c;起到了举足轻重的作用。如果选择不当&#xff0c;选到了劣质产品&#xff0c;尤其是化学行业或者食品行业&#xff0c;就有可能造成试剂失效或者是影响粮食食品安全问题…

2023英码科技激发团队活力,提升集体凝聚力团建拓展之旅圆满结束!

5月6日&#xff0c;时至立夏&#xff0c;风暖昼长&#xff0c;万物繁茂。 在这个生机盎然、活力四射的时节&#xff0c; 尤其适合出游&#xff0c;开展有益身心健康的活动。 这一天&#xff0c;英码科技全体家人们齐聚广州白云区钟落潭&#xff0c;开展一天好玩有趣又意义深…

SVN基本操作 使用教程

01-SVN概述 1、为什么需要SVN版本控制软件 2、解决之道 SCM&#xff1a;软件配置管理 所谓的软件配置管理实际就是对软件源代码进行控制与管理 CVS&#xff1a;元老级产品 VSS&#xff1a;入门级产品 ClearCase&#xff1a;IBM公司提供技术支持&#xff0c;中坚级产品 SVN&…

C++类与对象(三)

文章目录 一.初始化列表1.初始化列表的概念2.初始化列表的注意事项 二.explicit关键字1.单参数构造函数2.多参数构造函数 三.static成员1.static成员的概念2.static成员的特性 四.友元1.概念2.友元函数3.友元类 五.内部类1.概念2.内部类的性质 六.匿名对象七.拷贝对象时编译器的…

Docker安装MySQL主从配置

今天学习Docker安装MySQL主从配置 一、Master 1.1、拉取镜像 $docker pull mysql:8.0.25 1. 2、新建MySQL主服务器的容器实例&#xff0c;端口为3306 docker run -p 3306:3306 --name mysql-master \ -v /data/mysql/mysql-master/log:/var/log/mysql \ -v /data/mysql/mys…

WebSocket聊天功能小Demo

一、WebSocket简介 1.1 什么是WebSocket&#xff1f; WebSocket协议是基于TCP的一种网络协议&#xff0c;它实现了浏览器与服务器全双工&#xff08;Full-duplex&#xff09;通信。它允许服务端主动向客户端推送数据&#xff0c;这使得客户端和服务器之间的数据交换变得更加简…

模型微调的预处理

一.简历文本标注数据的准备 目标&#xff1a;把原始数据集转换为PaddleNLP支持的文本/文档抽取标注格式&#xff0c;为后续的模型微调做好准备。 工具&#xff1a;Label Studio 使用手册&#xff1a; applications/information_extraction/label_studio_text.md PaddlePad…

ai原创文章生成器-原创文章生成的软件

AI原创文章生成器——让你轻松批量生成高质量文章 随着内容创作的需求不断增加&#xff0c;人工撰写也难以满足快速高效的产出需求。在这种情况下&#xff0c;AI原创文章生成器应运而生&#xff0c;为人们创造了一种全新的自动化创作方式。下面我们就来了解一下这个神奇的工具…

无网络要求有网就能免费体验ChatGPT/GPT4

ChatGPT 是 OpenAI 公司开发的一款聊天机器人。它基于 OpenAI 的 GPT-3 语言模型,可以进行开域的自然语言聊天。主要特点如下: 开域聊天:ChatGPT可以聊任意话题,不需要预先定义话题范围或关键词,真正实现开放领域聊天。自然语言交互:ChatGPT可以理解并生成自然的语言表达,其对…