暴力递归转动态规划(十一)

news2024/11/18 11:22:18

题目1:
这篇帖子中有多道题,由浅入深。
arr是货币数组,其中的值都是正数。再给定一个正数aim。每个值都认为是一张货币,即便是值相同的货币也认为每一张都是不同的,返回组成aim的方法数。
例如:arr = {1,1,1},aim = 2
第0个和第1个能组成2,第1个和第2个能组成2,第0个和第2个能组成2
一共就3种方法,所以返回3

暴力递归
这道题相对来讲比较基础,很简单的从左往右尝试模型,给定的arr数组中,从左向右依次尝试,每个arr[index]一共就2种情况:要 和 不要。
并且确定好base case(何时终止递归):
第一种情况就是当index = arr.length时, 我的数组已经到尾了,取不出来东西了。
第二种是如果使用了当前arr[index]处的值,则用aim - arr[index]后,用rest(剩余钱数)向下传递,如果rest 为 0时,说明正好凑够了这个钱,也return。

代码

 public static int coinWays(int[] arr, int aim) {
        return process(arr, 0, aim);
    }

    public static int process(int[] arr, int index, int rest) {
        if (rest < 0) {
            return 0;
        }
        if (index == arr.length) {
            return rest == 0 ? 1 : 0;
        } else {
        	// process(arr, index + 1, rest) 不要当前的钱
        	// process(arr, index + 1, rest - arr[index]) 要当前的钱,则剩余钱数rest要 减去 arr[index]
            return process(arr, index + 1, rest) + process(arr, index + 1, rest - arr[index]);
        }
    }

动态规划
根据上面暴力递归代码改写动态规划,可变参数是index(数组下标)和 rest(剩余钱数),并且index可以到达arr.length的位置,rest也可能会为0,所以可以确定 dp[][] 大小为 dp[arr.length + 1][aim + 1]。
根据base case 可以确定 dp[arr.length][0]位置的值是1,其余的按照顺序遍历填充即可。

代码

    public static int dp(int[] arr, int aim) {
        if (aim == 0) {
            return 1;
        }
        int N = arr.length;
        int[][] dp = new int[N + 1][aim + 1];

        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] + (rest - arr[index] >= 0 ? dp[index + 1][rest - arr[index] ]: 0);
            }
        }
        return dp[0][aim];
    }

题目2
arr是面值数组,其中的值都是正数且没有重复。再给定一个正数aim。每个值都认为是一种面值,且认为张数是无限的。返回组成aim的方法数
例如:arr = {1,2},aim = 4
方法如下:1+1+1+1、1+1+2、2+2
一共就3种方法,所以返回3

暴力递归
整体思路依然是先从暴力递归代码开始写起,并根据暴力递归代码改写动态规划。
暴力递归方法的整体思路是这样:
依然是数组从左向右的不停尝试,因为数组中每个数值张数都可以当做是无限的,所以利用循环来看当前数值的数使用0张情况,使用1张情况,使用2张情况。。。 将结果值进行累加,即为总方法数。
所以暴力递归方法需要的参数:1. arr数组 2.rest 剩余钱数 3. index 数组下标
确定了暴力递归的尝试方法后,base case也就自然而然的出来了,那就是当 index = arr.length时,数组中没有钱可以取了,此时如果剩余钱数 rest = 0,说明利用数组中数值正好拼凑出来了正好的钱数,return 1。 否则 return 0。

代码

public static int coinsWay(int[] arr, int aim) {
        if (arr == null || arr.length == 0) {
            return -1;
        }
        return process(arr, aim, 0);
    }

    public static int process(int[] arr, int rest, int index) {
        if (index == arr.length) {
            return rest == 0 ? 1 : 0;
        }
        int ways = 0;
        //循环:当前数值从0张开始,不停尝试, 条件是 当前面值的钱使用几张,都不可以超过剩余钱数 rest。
        for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {
            ways += process(arr, rest - zhang * arr[index], index + 1);
        }
        return ways;

    }

动态规划
根据暴力递归方法代码可以确定出可变参数为剩余钱数 rest 和 数组下标 index。
又因为代码中 index是可以到达数组arr的长度的,并且剩余钱数rest可以为 0 。所以dp[][] 的范围是 dp[arr.length + 1] [ rest + 1]。

第二步要根据base case来给dp表进行初始化赋值。代码中 当index = arr.length时,如果rest = 0 则return 1。其余情况 return 0。
int[] 创建后默认值就是 0 ,所以 dp[ arr.length ][ 0 ]位置的值 = 1。 其余照搬暴力递归代码即可。

代码

 public static int dp(int[] arr, int aim) {
        if (arr == null || arr.length == 0) {
            return -1;
        }

        int N = arr.length;
        int[][] dp = new int[N + 1][aim + 1];

        dp[N][0] = 1;

        for (int index = N - 1; index >= 0; index--) {
            for (int rest = 0; rest <= aim; rest++) {
                int ways = 0;
                for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {
                    ways += dp[index + 1][rest - zhang * arr[index]];
                }
                dp[index][rest] = ways;
            }
        }
        return dp[0][aim];
    }

优化

关于上面的代码。逻辑上已经跑通了,但是关于dp表的整体构建生成还是过于复杂,因为zhang的for中枚举了数组中的每个数值的从 0 ~ zhang * arr[index] <= rest的所有情况。
如果有从暴力递归转动态规划(一)看到现在的会有发现,早期文章中整体的解题过程是 暴力递归 -》 傻缓存(记忆化搜索 - 看dp表中是否有当前值,没有则进行计算) -》 严格表结构依赖(根据每个格子的依赖关系,从底层向上构建dp表)
没有枚举过程时,傻缓存和严格表结构依赖的时间复杂度相同
但是这道题不太一样,因为之前的题目中,构建dp表中每个位置都是一个常数时间操作,而这道题里。构建dp表的每个格子都要进行for循环,这种情况下,如果不进行优化,那么时间复杂度大大增加(时间复杂度看枚举过程中的分支数量)。

思路
这种优化最简单直观的方法就是进行画图。将暴力递归方法中代码,根据依赖关系,直观的展现在图示中。
在这里插入图片描述
拿这张图举例,当rest = 11时(√位置),来看下它的依赖关系,根据暴力递归代码 index + 1 ,rest - zhang * arr[index],最开始使用的是0张,所以是rest - 0,那么在表中的依赖关系就是a位置,接下来是来使用1张、使用2张、使用3张。分别依赖的是bcd的位置。
那当rest = 8 时呢?此时使用1张2张3张,依赖的就是bcd位置。那 √ 位置的结果,之前是a+b+c+d,找到依赖关系后就可以发现,此时用 x + a = √。依赖关系已找到,可以根据这种严格的表结构依赖来进行优化。

代码

public static int bestDP(int[] arr, int aim) {
        if (arr == null || arr.length == 0) {
            return -1;
        }
        int N = arr.length;
        int[][] dp = new int[N + 1][aim + 1];

        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];
                //如果左边不越界
                if (rest - arr[index] >= 0) {
                    dp[index][rest] += dp[index][rest - arr[index]];
                }
            }
        }
        return dp[0][aim];
    }

题目3
arr是货币数组,其中的值都是正数。再给定一个正数aim。
每个值都认为是一张货币,认为值相同的货币没有任何不同,返回组成aim的方法数
例如:arr = {1,2,1,1,2,1,2},aim = 4 方法:1+1+1+1、1+1+2、2+2
一共就3种方法,所以返回3。

暴力递归
这个题目和第二题类似,区别在于第二题中每种面值是无限张的,不过这里是有限张数。所以这道题的整体解题思路也和第二题类似。

  1. 先将给定数组进行封装,封装的类中有 int[] coins类型 代表每种不同的面值,以及 int[] zhangs 代表着每种面值对应的张数。
  2. 暴力递归过程也和第二题类似,不过需要判断,for循环中每种面值使用的张数不能大于 zhangs[index] 中的值,以及 zhang * coins[index] 也要不能大于rest。

代码

//将给定的arr封装成Info对象
static class Info {
        int[] coins;
        int[] zhangs;

        public Info(int[] coins, int[] zhangs) {
            this.coins = coins;
            this.zhangs = zhangs;
        }

        public int[] getCoins() {
            return coins;
        }

        public int[] getZhangs() {
            return zhangs;
        }
    }
	
	// arr转换成Info的方法类
    public static Info getInfo(int[] arr) {
        Map<Integer, Integer> infoMap = new HashMap<>();

        for (int num : arr) {
            if (!infoMap.containsKey(num)) {
                infoMap.put(num, 1);
            } else {
                infoMap.put(num, (infoMap.get(num) + 1));
            }
        }

        int N = infoMap.size();
        int[] coins = new int[N];
        int[] zhangs = new int[N];

        int index = 0;

        for (Map.Entry<Integer, Integer> entries : infoMap.entrySet()) {
            coins[index] = entries.getKey();
            zhangs[index++] = entries.getValue();
        }
        return new Info(coins, zhangs);
    }

	//暴力递归主方法
    public static int coinsWay(int[] arr, int aim) {
        if (arr == null || arr.length == 0) {
            return -1;
        }
        Info info = getInfo(arr);

        return process(info.getCoins(), info.getZhangs(), aim, 0);
    }

    public static int process(int[] coins, int[] zhangs, int rest, int index) {
        if (index == zhangs.length) {
            return rest == 0 ? 1 : 0;
        }

        int ways = 0;
        // 判断的逻辑: 
        //zhang 要 <= 每种面值给定的张数
        //rest - 面值 * 使用的张数 >= 0			
        for (int zhang = 0; zhang <= zhangs[index] && zhang * coins[index] <= rest; zhang++) {
            ways += process(coins, zhangs, rest - zhang * coins[index], index + 1);
        }
        return ways;
    }

动态规划
动态规划思路也和第二题大致相同,根据可变参数index和rest确定dp表范围,而后循环遍历填充dp表。

代码

 public static int dp(int[] arr, int aim) {
        if (arr == null || arr.length == 0) {
            return -1;
        }
        Info info = getInfo(arr);

        int[] zhangs = info.getZhangs();
        int[] coins = info.getCoins();

        int N = zhangs.length;

        int[][] dp = new int[N + 1][aim + 1];

        dp[N][0] = 1;

        for (int index = N - 1; index >= 0; index--) {
            for (int rest = 0; rest <= aim; rest++) {
                int ways = 0;
                for (int zhang = 0; zhang <= zhangs[index] && zhang * coins[index] <= rest; zhang++) {
                    ways += dp[index + 1][rest - zhang * coins[index]];
                }
                dp[index][rest] = ways;
            }
        }
        return dp[0][aim];
    }

优化
动态规划中又见到了填充dp表时的枚举过程,所以可以根据严格的表结构依赖,根据画图,找到每个格子在dp表中的依赖关系,从而替代代码中的枚举for循环。
需要注意的是:这次的优化和第二题不同的在于,每种张数是固定的,而第二题每种张数是无限张的
所以,要考虑到张数的限制,还是回归到图示当中去。
数组中面值有 1,3,5 每种面值各2张,依然是rest = 11,面值 = 3时为例。
在这里插入图片描述

当rest = 11时,面值为3的情况一共有abc三种,对应着3面值的数使用了0、1、2张的情况。
再来看X的位置,此时情况为bcd,第二题要想求√位置,需要 x + a,因为是无限张,但是本题中。如果依然是X + a,那么会多加一个d,张数的使用会多一张。所以在求√时,需要将d位置减掉。
接下来我们将它抽象化一下,根据暴力递归的代码带入公式。
假设此时是m面值,共有n张 , √此时在 i 行,剩余钱数rest,。
那此时a位置就是:dp[index + 1][rest]
此时的x位置就是: dp[index][rest - 一张面值]
d的位置就是: dp[index + 1][ rest - 一张面值 * (面值张数 + 1)]

代码

public static int beatDP(int[] arr, int aim) {
        Info info = getInfo(arr);

        int[] zhangs = info.getZhangs();
        int[] coins = info.getCoins();

        int N = zhangs.length;

        int[][] dp = new int[N + 1][aim + 1];

        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];
                //如果左侧也有,那么就 累加
                if (rest - coins[index] >= 0) {
                    dp[index][rest] += dp[index][rest - coins[index]];
                }
                //如果左侧不越界,则减去多余的那一个
                if (rest - coins[index] * (zhangs[index] + 1) >= 0) {
                    dp[index][rest] -= dp[index + 1][rest - coins[index] * (zhangs[index] + 1)];
                }
            }
        }
        return dp[0][aim];
    }

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

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

相关文章

企业文件防泄密方法

企业文件防泄密方法 安企神数据防泄密系统下载使用 企业文件是企业的核心资产&#xff0c;其中可能包含大量的敏感信息&#xff0c;如客户资料、产品配方、财务数据等。一旦这些文件泄露&#xff0c;可能会给企业带来不可估量的损失。 然而&#xff0c;企业文件防泄密是确保…

气膜场馆里面噪声很大怎么解决?

随着气膜结构在各个领域的广泛应用&#xff0c;人们开始意识到在这些场馆内部&#xff0c;特别是在大型活动和展览中&#xff0c;噪声问题可能会变得相当严重。传统的气膜结构通常难以提供良好的声学环境&#xff0c;这对于参与者的舒适度和活动的质量构成了挑战。为了解决气膜…

QECon大会亮相产品,支持UI自动化测试?RunnerGo

最近在gitee上看见一款获得GVP&#xff08;最有价值开源项目&#xff09;的测试平台RunnerGo&#xff0c;看他们官网介绍包含了接口测试、性能测试、自动化测试。知道他们有saas版可以试用&#xff0c;果断使用了一下&#xff0c;对其中场景管理和性能测试印象深刻&#xff0c;…

Wmware虚拟机网络配置

Wmware虚拟机网络配置 这几天我在家里电脑安装虚拟机打算学习一下集群配置&#xff0c;出现了一些问题。现在想把它记录下来&#xff0c;如果能给看到的人一些帮助&#xff0c;那就更好了。 1、桥接模式的配置 这个时候 我们的虚拟机就是桥接模式上网了。这时候可能会出现不能…

vue 获取年龄

今天对接完成百度身份证号识别相关API后&#xff0c;需要从身份中的出生年月日业获取其年龄&#xff0c;封装如下方法。 实现代码&#xff1a;ageValue()接受一个出生字符串 export function ageValue(val) {// 新建日期对象let date new Date()// 今天日期&#xff0c;数组&a…

如何快速搭建springboot+前后端分离(vue),客户端实现微信小程序+ios+app使用uniapp(一处编写,处处编译)

kxmall 针对中小商户、企业和个人学习者开发。使用Java编码&#xff0c;采用SpringBoot、Mybatis-Plus等易用框架&#xff0c;适合个人学习研究。同时支持单机部署、集群部署&#xff0c;用户与店铺范围动态定位&#xff0c;中小商户企业可根据业务动态扩容。kxmall使用uniapp前…

goctl 安装步骤

goctl&#xff1a;go-zero框架强大的项目脚手架工具&#xff0c;一个简单易用的代码生成工具。 go-zero官网&#xff1a;https://go-zero.dev/ go-zero 官网上面对 goctl 的介绍&#xff1a;goctl读作go control&#xff0c;不要读成go C-T-L。goctl的意思是不要被代码控制&a…

学习笔记|配对样本均数T检验|SPSS常用的快捷键|规范表达|《小白爱上SPSS》课程:SPSS第六讲 | 配对样本均数T检验

目录 学习目的软件版本原始文档配对样本均数T检验一、实战案例二、案例解析三、统计策略四、SPSS操作1、正态性检验2、配对样本T检验 五、结果解读六、规范报告1、规范表格2、规范文字 划重点Tips:SPSS常用的快捷键 学习目的 SPSS第六讲 | 配对样本均数T检验 软件版本 IBM S…

杀毒软件哪个好,杀毒软件有哪些

安全杀毒软件是一种专门用于检测、防止和清除计算机病毒、恶意软件和其他安全威胁的软件。这类软件通常具备以下功能&#xff1a; 1. 实时监测&#xff1a;通过实时监测计算机系统&#xff0c;能够发现并防止病毒、恶意软件等安全威胁的入侵。 2. 扫描和清除&#xff1a;可以…

微信定时发圈,快人一步不落索

现在的社交媒体运营已经成为了私域流量获取的重要手段&#xff0c;而微信作为最大的社交平台之一&#xff0c;更是吸引了众多使用者。但是&#xff0c;你是否曾经感叹过每天手动发朋友圈的繁琐&#xff1f;是否希望能够事先设置好定时发送的功能&#xff0c;让你的朋友圈自动更…

SDP协议分析

目录 SDP的结构SDP语法必需字段可选字段字段顺序子字段 3.SDP例子 1. SDP的结构 SDP&#xff08;Session Description Protocol&#xff09;完全是⼀种会话描述格式&#xff0c;它不属于传输协议&#xff0c;它只使⽤于适当的传输协议&#xff0c;包括会话通知协议&#xf…

腾讯云双11云服务器大促优惠活动:超多云服务器优惠惊喜不断!

腾讯云双11大促优惠活动已经拉开帷幕&#xff0c;为广大用户带来了一系列的超值优惠。活动时间从现在起一直延续到2023年11月30日23:59:59&#xff0c;让用户有足够的时间去选择和购买心仪的产品。活动入口链接为https://1111.mian100.cn&#xff0c;点击链接即可进入活动页面。…

【错误解决方案】ModuleNotFoundError: No module named ‘transformers‘

1. 错误提示 在python程序中&#xff0c;尝试导入一个名为transformers的模块&#xff0c;但Python提示找不到这个模块。 错误提示&#xff1a;ModuleNotFoundError: No module named ‘transformers‘ 2. 解决方案 所遇到的问题是Python无法找到名为transformers的模块&am…

联发科MT6893(天玑1200)_MTK5G芯片规格参数性能_安卓手机主板方案

联发科天玑1200集成MediaTek 5G调制解调器&#xff0c;通过包含6大维度、72个场景测试的德国莱茵TV Rheinland认证&#xff0c;支持高性能5G连接&#xff0c;带给用户全场景的高品质5G连网体验。 进入5G时代&#xff0c;AI多媒体成为主流应用&#xff0c;天玑1200以强劲的平台…

第十届集美大学程序设计竞赛正式赛 C题 方格染色 题解

前言 记录一下&#xff0c;给自己长长记性&#xff0c;刚看到题目立马想出了一个 O ( T ∗ n k ) O(T*nk) O(T∗nk)的暴力DP&#xff0c;想着构造个矩阵用快速幂优化为 O ( ∑ 1 T k ∗ l o g ( n ) ) O(\sum_1^T{k}*log(n)) O(∑1T​k∗log(n))&#xff0c;结果构造一个小时发…

单目标应用:红尾鹰算法(Red‑tailed hawk algorithm ,RTH)求解微电网优化MATLAB

一、微网系统运行优化模型 微电网优化模型介绍&#xff1a; 微电网多目标优化调度模型简介_IT猿手的博客-CSDN博客 二、红尾鹰算法RTH 红尾鹰算法&#xff08;Red‑tailed hawk algorithm &#xff0c;RTH&#xff09;由Seydali Ferahtia等人于2023年提出&#xff0c;该算法…

医院等级评审,离不开不良事件报告系统

不良事件上报系统源码 不良事件管理系统源码 不良事件上报系统是医院等级评审、评价、监督、保障和提高医疗服务质量的重要举措&#xff0c;它作为促进医疗质量持续改进的有效手段&#xff0c;受到越来越多医院的高度重视。在卫生部三级综合医院评审标准实施细则3.9.1明确要求&…

【Linux】第六站:Centos系统如何安装软件?

文章目录 1.Linux安装软件的方式2.Linux的软件生态3. yum4. rzsz软件的安装与卸载5.yum如何知道去哪里下载软件&#xff1f; 1.Linux安装软件的方式 在linux中安装软件常用的有三种方式 源代码安装&#xff08;我们还需要进行编译运行后才可以&#xff0c;很麻烦&#xff09; …