递归到动态规划:省去枚举行为

news2024/11/28 12:34:34

如果在动态规划的过程中没有枚举行为,那严格位置依赖和傻缓存的方式并没有太大区别,但是当有枚举行为的时候(一个位置依赖于多个位置),那严格位置依赖是有优化空间的,枚举行为也许可以省去,题目:

arr是面值数组,其中的值都是正数且没有重复。再给定一个正数aim

每个值都认为是一种面值,且认为张数是无限的。

返回组成aim的方法数

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

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

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

这个题目的动态规划普遍位置({1,8})的依赖,我们原来dp[1][8] = dp [2][8] + dp[2][6] + dp[2][4] + dp[2][2] + dp[2][0]

而dp[1][6] = dp[2][6] + dp[2][4] + dp[2][2] + dp[2][0]

我们可以看到计算dp[2][8]时候用到的 dp[2][6] + dp[2][4] + dp[2][2] + dp[2][0]之前其实是计算过的,这个值就是dp[1][6]

所以可以简化为dp[1][6] + dp[2][8]

普遍位置就是dp[index][rest] = dp[index+1][rest] + dp[index][rest-arr[index]]

dp[index][rest-arr[index]]这个要先判断存在不存在

也就是它依赖于它的下方和左边,dp数组按照从下到上,从左到右的顺序初始化即可

 对应的代码如下:

package dataStructure.recurrence.practice;

/**
 * arr是面值数组,其中的值都是正数且没有重复。再给定一个正数aim。
 * 每个值都认为是一种面值,且认为张数是无限的。
 * 返回组成aim的方法数
 * 例如:arr = {1,2},aim = 4
 * 方法如下:1+1+1+1、1+1+2、2+2
 * 一共就3种方法,所以返回3
 */
public class CoinsWayNoLimit {
    public static int coinsWay(int[] arr, int aim) {
        return process1(arr, 0, aim);
    }

    /**
     * 动态规划的解法-原始版
     * 根据递归,可变的参数是index和rest,变化范围分别是0~arr.length, 0~rest
     * @param arr 原始的数组
     * @param aim 要组成的目标
     * @return
     */
    public static int coinsWayDp(int[] arr, int aim) {
        int[][] dp = new int[arr.length + 1][aim + 1];
        //最后一行只有0位置是1,其他都是0(0是int默认值,不需要初始化)
        dp[arr.length][0] = 1;
        //根据递归,所有的(index, rest)都依赖于下一行前面的某个位置
        //所以行必须从下往上,列初始化的顺序无所谓
        for(int index = arr.length - 1; index >=0; index --) {
            for(int rest = 0; rest <= aim; rest ++) {
                int ways = 0;
                for(int num = 0; num * arr[index] <= rest; num ++) {
                    ways += dp[index + 1][rest - (num * arr[index])];
                }
                dp[index][rest] = ways;
            }
        }
        return dp[0][aim];
    }

    /**
     * 动态规划的解法-原始版
     * 根据递归,可变的参数是index和rest,变化范围分别是0~arr.length, 0~rest
     * @param arr 原始的数组
     * @param aim 要组成的目标
     * @return
     */
    public static int coinsWayDpBest(int[] arr, int aim) {
        int[][] dp = new int[arr.length + 1][aim + 1];
        //最后一行只有aim位置是1,其他都是0(0是int默认值,不需要初始化)
        dp[arr.length][0] = 1;
        //根据递归,所有的(index, rest)都依赖于下一行前面的某个位置
        //所以行必须从下往上,这里我们要省掉枚举行为,一个位置依赖于他下面的位置和他前面的某个位置,所以必须从前往后
        for(int index = arr.length - 1; index >=0; index --) {
            for(int rest = 0; rest <= aim; rest ++) {
                //这是倒数第二行,他下面肯定有位置
                dp[index][rest] = dp[index + 1][rest];
                //但是左边的位置rest-arr[index]不一定存在,所以要做判断
                if(rest-arr[index] >= 0) {
                    //如果存在就加上
                    dp[index][rest] += dp[index][rest-arr[index]];
                }
            }
        }
        return dp[0][aim];
    }
    /**
     * 递归黑盒方法,从index号下标开始组成left
     * @param arr 原始的面值数组,每个面值都是无限的
     * @param index 当前要考虑的位置下标
     * @param rest 还差多少钱
     * @return
     */
    public static int process1(int[] arr, int index, int rest) {
        if(rest < 0) return 0;
        if(index == arr.length) {
            return rest == 0? 1 : 0;
        }
        int ways = 0;
        for(int num = 0; num * arr[index] <= rest; num++) {
            ways += process1(arr, index + 1, rest - (num * arr[index]));
        }
        return ways;
    }

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

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

省去了枚举行为,结果完全一致,原来的时间复杂度是O(N * M * K),现在的话变成了O(N * M)

其中K是rest/数组中最小的那个面值

个人的总结是:如果某个位置只依赖它的90度角范围内的枚举都是可以优化的(上、左  上、左上、左 等等)

欢迎私信讨论

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

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

相关文章

Docker(一)

Docker Docker简述 传统虚拟机技术基于安装在主操作系统上的虚拟机管理系统&#xff0c;创建虚拟机&#xff08;虚拟出各种硬件&#xff09;&#xff0c;在虚拟机上安装从操作系统&#xff0c;在从操作系统中安装和部署各种应用。这种方式占用资源很大并且步骤冗余。在此基础之…

跟着LearnOpenGL学习2--三角形绘制

文章目录 一、前言二、图形渲染管线2.1、顶点数据2.2、顶点着色器2.3、形状&#xff08;图元&#xff09;装配2.4、几何着色器2.5、光栅化2.6、片段着色器2.7、测试与混合 三、渲染流程3.1、顶点输入3.2、顶点着色器3.3、编写、编译着色器3.4、片段着色器3.5、着色器程序3.6、链…

专业专注,极致体验,高端隐形智能晾衣机品牌邦先生官宣浙江卫视知名主持人沈涛为品牌代言人

5月11日&#xff0c;高端隐形晾衣架领导品牌邦先生正式宣布&#xff0c;浙江卫视知名主持人沈涛为品牌代言人&#xff0c;以更高标准的晾晒&#xff0c;共同迎接智能晾晒大时代&#xff0c;用科技力量创造美好智慧家居生活。 专业实力品牌邦先生王牌主持沈涛 作为浙江卫视的“王…

GPT Prompt(提示词)写法与教程,相关站点与工具

文章目录 1、Prompt工程师&#xff08;提示工程师&#xff09;2、提示词教程3、提示词工具&#xff08;中文&#xff09;4、提示词工具&#xff08;英文&#xff09; 1、Prompt工程师&#xff08;提示工程师&#xff09; Prompt工程师&#xff0c;也称为AI提示工程师&#xff…

纸质文件怎么扫描成电子版?简单小妙招快来拿捏

随着科技的发展&#xff0c;越来越多的人将纸质文件转换为电子版&#xff0c;以方便存储和共享。本文将介绍纸质文件如何扫描成电子版&#xff0c;以及如何利用手机进行扫描转换。 纸质文件扫描成电子版 将纸质文件扫描成电子版是一种常见的方式。首先&#xff0c;您需要一台扫…

JAVA-代码块和内部类

文章目录 目录 文章目录 前言 1.代码块 1.1什么是代码块? 1.2代码块的分类及作用: 1.静态代码块 2.成员代码块(又叫做构造代码块) 3.局部代码块 2.内部类 2.1 什么是内部类? 2.2 内部类的分类 1.成员内部类 2.静态内部类 3.匿名内部类 4.局部内部类 总结 前言 作者简介:我是最…

MySQL索引优化(超详细)

Mysql索引优化 1 索引介绍 1.1 什么时MySQL的索引 ​ MySQL官方对于索引的定义:索引是帮助MySQL高效获取数据的数据结构。 ​ MySQL在存储数据之外&#xff0c;数据库系统中还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种引用(指向)表中的数据&#xff…

Prometheus

Prometheus简介 prometheus是一个监控、告警的开源系统。Prometheus收集并存储时序的指标数据。指标数据存储伴随一个timestamp和可选择key-values 队列标签 Prometheus特性&#xff1a; 一个时序的多维数据模型&#xff0c;被mertic name和 key/value pairs标签唯一定义 P…

将DenseNet换成Resnet——更换深度学习骨干网络

最近我在学习一个手写公式识别的网络&#xff0c;这个网络的backbone使用的是DenseNet&#xff0c;我想将其换成ResNet 至于为什么要换呢&#xff0c;因为我还没换过骨干网络&#xff0c;就像单纯拿来练练手&#xff0c;增加我对网络的熟悉程度&#xff0c;至于会不会对模型的性…

【时间序列数据挖掘】ARIMA模型

目录 0、前言 一、移动平均模型MA 二、自回归模型AR 三、自回归移动平均模型ARMA 四、自回归移动平均模型ARIMA 【总结】 0、前言 传统时间序列分析模型&#xff1a; ARIMA模型是一个非常灵活的模型&#xff0c;对于时间序列的好多特征都能够进行描述&#xff0c;比如说平…

5.11黄金最新行情走势分析及多空交易策略

近期有哪些消息面影响黄金走势&#xff1f;本周黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;北京时间周三(5月10日)20:30&#xff0c;美国劳工部公布4月通胀报告&#xff0c;整体与核心CPI年率都走低&#xff0c;支持美联储6月份保持利率不变。数据显示&…

RabbitMQ详解(一):Linux安装

消息队列概念 消息队列是在消息的传输过程中保存消息的容器。队列的主要目的是提供路由并保证消息的传递&#xff1b;如果发送消息时接收者不可用&#xff0c;消息队列会保留消息&#xff0c;直到可以成功地传递它。 常见的消息队列 RabbitMQ 基于AMQP(高级消息队列协议)基础上…

不要轻易放弃丢失的U盘文件夹数据,这里有按文件夹恢复数据的技巧

U盘&#xff0c;全名叫USB闪存盘&#xff0c;是一种便携式的存储设备&#xff0c;是一种可以插入到电脑等电子设备上进行数据传输和存储的硬件设备。U盘的使用方便、速度高、存储容量大、稳定性高&#xff0c;因此被广泛用于数据备份、文档传输、音频视频存储等方面。但是使用过…

easyrecovery免费版2023最新电脑数据恢复软件

通常&#xff0c;许多人会将工作或生活中的数据存储在我们的计算机上。很多时候&#xff0c;由于我们的误操作或其他一些问题&#xff0c;很容易错误地删除一些文件和数据。特别是&#xff0c;一些计算机故障总是会导致数据丢失&#xff0c;这是非常麻烦的。当需要重新安装系统…

【TA100】5 纹理的秘密

1 是什么&#xff1f; 2 为什么使用纹理 3 纹理管线 纹理投影 展开UV到UV坐标系 模型坐标> uv坐标 > 乘分辨率(256 256) > 颜色采样 4 纹理模式 重复&#xff0c;镜像重复&#xff0c;边界拉伸&#xff0c;填充颜色 5 采样模式 它决定了当纹理由于变换而产生拉伸时&a…

go小技巧(易错点)集锦

目录 len的魔力评论区大佬解答答案详解 结构体是否相等答案解析&#xff1a;结构体比较规则举例 常量的编译我的答案标准答案内存四区概念&#xff1a; new关键字答案 iota的魔力结果解析可跳过的值定义在一行中间插队 小结iota详解iota 原理iota 规则依赖 const按行计数多个io…

云数据库技术沙龙|多云多源下的数据复制技术解读-NineData

摘要&#xff1a;随着数据智能时代的到来&#xff0c;多云多源架构下的数据管理是企业必备的基础设施&#xff0c;我们认为数据存取、数据集成与分发、数据安全与数据质量是基础&#xff0c;也是走向多云多源架构的起点。本议题介绍云原生的多云多源数据管理NineData&#xff0…

PlSql存储过程基础

目录儿 常用指令1. 什么是PLSQL语言2. PLSQL程序结构2.1 第一个程序 HelloWord:2.2 执行程序2.2.1 在工具中执行2.2.2 在sqlplus客户端中执行(命令行) 3. 变量3.1 普通变量3.2 引用型变量3.3 记录型变量 4. 流程控制4.1 条件分支4.2 循环 5. 游标5.1 定义5.2 语法5.3 游标的属性…

或许你需要这套uni-app打包android与ios流程

1、hbuilder每个账户的每日云打包有上限次数限制&#xff0c;超出次数要么换账户要么换成本地打包(uni-app提供了足够多云端的打包次数) 2、android打包&#xff0c;也就是apk包 优先搞明白两个需求&#xff1a; 、android包名是否为默认值&#xff0c;如果是默认值&#xf…

基于轻量化深度学习网络的工业环境小目标缺陷检测

源自&#xff1a;控制与决策 作者&#xff1a;叶卓勋 刘妹琴 张森林 摘 要 工业环境下表面缺陷检测是质量管理的重要一环, 具有重要的研究价值.通用检测网络(如YOLOv4)已被证实在多种数据集检测方面是有效的, 但是在工业环境的缺陷检测仍需要解决两个问题: 一是缺陷实例在…