算法32:针对算法31货币问题进行扩展,并对从左往右模型进行总结

news2024/11/5 15:20:05

本算法是在算法31的基础之上进行推理总结的,因此,在看本章之前,必须先去了解算法31,否则会觉得莫名其妙。

算法31的推理过程:

如果 x = y1 + y2 + y3 + y4 + y5 + y6.   x1 = y2 + y3 + y4 + y5 + y6

那么 x = y1 + x1.   

根据以上推导公式,可以对时间复杂度进行优化。

之前我们对从左往右模型进行过总结,即:

1. 针对固定集合,值不同,就是讨论要和不要的累加和。算法30有完整的例子

2. 针对非固定集合,面值固定,张数无限。口诀就是讨论要与不要,要的话逐步讨论要几张的累加和。算法31有完整的例子

今天,我们讨论最后一种情况,即:

3. 针对非固定集合,面值固定,张数随机。也就是说有可能只有0张,1张,2张,甚至也是无限的情况。口诀就是在口诀2的基础之上要去除多余项

题目:

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都是面值相同的,没有什么区别。

举个例子:给你6个1毛钱硬币,一个5毛钱硬币,要求你列举出能凑成1元钱的组合。答案肯定是1种呀,你不可能说每个1毛钱都是不一样,6个一毛轮流拿掉一个,剩余的和5毛钱组合,总共有5种组合方法吧。

下面说一下今天的推导公式。

假设某一行的value为3, 张数为2,aim还是15.  

那么基于算法31,我们可以对算法32进行假设:

是不是会有人问, 算法31不是会把y4,y5,y6等等情况都列举出来的吗,为什么本章算法就假设了value为3,张数只为2的情况呢。因为本算法每个面值的张数是不固定的,随机的。如果张数足够多,那逻辑就和算法31一样了。

思路:

1. 首先,我们需要对数组的每个面值以及对应的张数进行统计

2. 在讨论要不要,以及要几张的时候,需要在算法31的基础之上考虑实际可能存在的张数。

递归代码:

static class Info {
        int[] value;
        int[] zhangshu;

        Info (int[] k, int[] v) {
            value = k;
            zhangshu = v;
        }
    }

    public static Info getInfo (int[] arr) {
        Map map = new HashMap<Integer, Integer>();
        for (int i = 0; i < arr.length; i++) {
            int v = arr[i];
            if (map.get(v) != null) {
                map.put(v, (int) map.get(v) + 1);
            }
            else {
                map.put(v, 1);
            }
        }

        int[] k = new int[map.size()];
        int[] v = new int[map.size()];
        int index = 0;
        for (Iterator iterator = map.keySet().iterator(); iterator.hasNext();) {
            int key = (int) iterator.next();
            int value = (int) map.get(key);
            k[index] = key;
            v[index++] = value;
        }
        return new Info(k, v);
    }

    public static int ways(int[] arr, int aim)
    {
        if (arr == null || arr.length == 0 || aim < 0) {
            return 0;
        }
        Info info = getInfo(arr);
        return process(info.value, info.zhangshu, 0, aim);
    }

    public static int process (int[] value, int[] zhangshu, int index, int aim)
    {
        //面值数组结束了
        if (index == value.length) {
            return aim == 0 ? 1 : 0;
        }

        int ways = 0;
        for (int zhang = 0; zhang <= zhangshu[index] && zhang * value[index] <= aim; zhang++) {
            ways += process(value, zhangshu, index+1, aim- zhang * value[index]);
        }

        return ways;
    }

动态规划版本:

//动态规划
    public static int ways2(int[] arr, int aim)
    {
        if (arr == null || arr.length == 0 || aim < 0) {
            return 0;
        }

        Info info = getInfo(arr);
        //数组值
        int[] value = info.value;
        //每个值对应的张数
        int[] zhangshu = info.zhangshu;

        int[][] dp = new int[value.length + 1][aim + 1];
        //最后一行的初始值
        dp[value.length][0] = 1;

        //数组值为行
        for (int row =  value.length - 1; row >= 0; row--) {
            //aim为列
            for (int col = 0; col <= aim; col++) {

                int ways = 0;
                for (int zhang = 0; zhang * value[row] <= col && zhang <= zhangshu[row]; zhang++) {
                    ways += dp[row + 1][col - (zhang * value[row])];
                }
                dp[row][col] = ways;
            }
        }
        return dp[0][aim];
    }

对动态规划进行时间复杂度优化

//动态规划 + 时间复杂度
    public static int ways3(int[] arr, int aim)
    {
        if (arr == null || arr.length == 0 || aim < 0) {
            return 0;
        }

        Info info = getInfo(arr);
        //数组值
        int[] value = info.value;
        //每个值对应的张数
        int[] zhangshu = info.zhangshu;

        int[][] dp = new int[value.length + 1][aim + 1];
        //最后一行的初始值
        dp[value.length][0] = 1;

        //数组值为行
        for (int row =  value.length - 1; row >= 0; row--) {
            //aim为列
            for (int col = 0; col <= aim; col++) {

                /**
                 * 此处的代码,就是 x = x1 + y1.
                 * 即包含了多余的值了
                 */
                dp[row][col] = dp[row + 1][col];
                if (col - value[row] >= 0) {
                    dp[row][col] += dp[row][col - value[row]];
                }

                /**
                 * row代表推理的value, col代表列的下标,即代表aim的值
                 *
                 * 如果col就是我们想要的值,那么我们必须根据张数往前找。
                 * 如果value为3,张数为2,col为15,
                 * 那么我们就应该得到下一行的列下标为 15, 12, 9的值。而
                 * 多余的下标就是下一行列为6的值。
                 *
                 * value数组代表面值不同的数组: 此处的value[row] = 3.
                 * zhangshu数组代表当前面值为3的张数。此处zhangshu[row] = 2.
                 * 那么多余的位置不就是:
                 * 15 - 3 *(2+1) = 6 吗?
                 */
                if (col - value[row] * (zhangshu[row] + 1) >= 0) {
                    //既然是多余的,那当然要减去多余的推导了。
                    dp[row][col] -= dp[row + 1][col - value[row] * (zhangshu[row] + 1)];
                }
            }
        }
        return dp[0][aim];
    }

完整代码以及添加对数器进行测试

package code03.动态规划_07.lesson4;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 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 ContainWaysLimitCountPaper_06 {

        static class Info {
            int[] value;
            int[] zhangshu;

            Info (int[] k, int[] v) {
                value = k;
                zhangshu = v;
            }
        }

        public static Info getInfo (int[] arr) {
            Map map = new HashMap<Integer, Integer>();
            for (int i = 0; i < arr.length; i++) {
                int v = arr[i];
                if (map.get(v) != null) {
                    map.put(v, (int) map.get(v) + 1);
                }
                else {
                    map.put(v, 1);
                }
            }

            int[] k = new int[map.size()];
            int[] v = new int[map.size()];
            int index = 0;
            for (Iterator iterator = map.keySet().iterator(); iterator.hasNext();) {
                int key = (int) iterator.next();
                int value = (int) map.get(key);
                k[index] = key;
                v[index++] = value;
            }
            return new Info(k, v);
        }

        public static int ways(int[] arr, int aim)
        {
            if (arr == null || arr.length == 0 || aim < 0) {
                return 0;
            }
            Info info = getInfo(arr);
            return process(info.value, info.zhangshu, 0, aim);
        }

        public static int process (int[] value, int[] zhangshu, int index, int aim)
        {
            //面值数组结束了
            if (index == value.length) {
                return aim == 0 ? 1 : 0;
            }

            int ways = 0;
            for (int zhang = 0; zhang <= zhangshu[index] && zhang * value[index] <= aim; zhang++) {
                ways += process(value, zhangshu, index+1, aim- zhang * value[index]);
            }

            return ways;
        }

    //动态规划
    public static int ways2(int[] arr, int aim)
    {
        if (arr == null || arr.length == 0 || aim < 0) {
            return 0;
        }

        Info info = getInfo(arr);
        //数组值
        int[] value = info.value;
        //每个值对应的张数
        int[] zhangshu = info.zhangshu;

        int[][] dp = new int[value.length + 1][aim + 1];
        //最后一行的初始值
        dp[value.length][0] = 1;

        //数组值为行
        for (int row =  value.length - 1; row >= 0; row--) {
            //aim为列
            for (int col = 0; col <= aim; col++) {

                int ways = 0;
                for (int zhang = 0; zhang * value[row] <= col && zhang <= zhangshu[row]; zhang++) {
                    ways += dp[row + 1][col - (zhang * value[row])];
                }
                dp[row][col] = ways;
            }
        }
        return dp[0][aim];
    }

    //动态规划 + 时间复杂度
    public static int ways3(int[] arr, int aim)
    {
        if (arr == null || arr.length == 0 || aim < 0) {
            return 0;
        }

        Info info = getInfo(arr);
        //数组值
        int[] value = info.value;
        //每个值对应的张数
        int[] zhangshu = info.zhangshu;

        int[][] dp = new int[value.length + 1][aim + 1];
        //最后一行的初始值
        dp[value.length][0] = 1;

        //数组值为行
        for (int row =  value.length - 1; row >= 0; row--) {
            //aim为列
            for (int col = 0; col <= aim; col++) {

                /**
                 * 此处的代码,就是 x = x1 + y1.
                 * 即包含了多余的值了
                 */
                dp[row][col] = dp[row + 1][col];
                if (col - value[row] >= 0) {
                    dp[row][col] += dp[row][col - value[row]];
                }

                /**
                 * row代表推理的value, col代表列的下标,即代表aim的值
                 *
                 * 如果col就是我们想要的值,那么我们必须根据张数往前找。
                 * 如果value为3,张数为2,col为15,
                 * 那么我们就应该得到下一行的列下标为 15, 12, 9的值。而
                 * 多余的下标就是下一行列为6的值。
                 *
                 * value数组代表面值不同的数组: 此处的value[row] = 3.
                 * zhangshu数组代表当前面值为3的张数。此处zhangshu[row] = 2.
                 * 那么多余的位置不就是:
                 * 15 - 3 *(2+1) = 6 吗?
                 */
                if (col - value[row] * (zhangshu[row] + 1) >= 0) {
                    //既然是多余的,那当然要减去多余的推导了。
                    dp[row][col] -= dp[row + 1][col - value[row] * (zhangshu[row] + 1)];
                }
            }
        }
        return dp[0][aim];
    }


    // 为了测试
    public static int[] randomArray(int maxLen, int maxValue) {
        int N = (int) (Math.random() * maxLen);
        int[] arr = new int[N];
        for (int i = 0; i < N; i++) {
            arr[i] = (int) (Math.random() * maxValue) + 1;
        }
        return arr;
    }

    // 为了测试
    public static void printArray(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }


    public static void main(String[] args) {
      /*  int[] arr = {1,2,1,1,2,1,2};
        int aim = 4;

        System.out.println(ways(arr, aim));
        System.out.println(ways2(arr, aim));*/

        int maxLen = 10;
        int maxValue = 20;
        int testTime = 1000000;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            int[] arr = randomArray(maxLen, maxValue);
            int aim = (int) (Math.random() * maxValue);
            int ans1 = ways(arr, aim);
            int ans2 = ways2(arr, aim);

            if (ans1 != ans2) {
                System.out.println("Oops!");
                printArray(arr);
                System.out.println(aim);
                System.out.println(ans1);
                System.out.println(ans2);

                break;
            }
        }
        System.out.println("测试结束");
    }
}

至于空间复杂度优化,可以参考算法31进行研究,看看此题是否还可以对空间复杂度进行优化

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

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

相关文章

欧洲编程语言四巨头

从左往右&#xff0c;依次是 尼克劳斯沃斯 (Niklaus Wirth)&#xff0c;迪杰斯特拉&#xff08;Edsger Dijkstra&#xff09;&#xff0c;霍尔&#xff08;Tony Hoare&#xff09; 尼克劳斯沃斯 (Niklaus Wirth) 瑞士人&#xff0c;一生发明了8种编程语言&#xff0c;其中最著…

【Python】Graphviz的安装和使用

graphviz包可以用来决策树可视化&#xff0c;只安装包之后直接import使用会报错&#xff0c;因为graphviz是一个要单独安装的软件。 下载路径&#xff1a;Download | Graphviz 有不同的版本&#xff0c;我这里用的是最新版 9.0版本安装之后可以选自动添加到环境变量——系统…

lazada越南站收款问题;lazada可以使用支付宝吗?-站斧浏览器

Lazada越南站收款问题 线上支付方式&#xff1a;Lazada越南本土店提供多种线上支付方式&#xff0c;以方便消费者完成购物支付。常见的线上支付方式包括信用卡支付、借记卡支付、电子钱包支付&#xff08;如Momo、Zalo Pay等&#xff09;以及银行转账等。商家可以根据自己的需…

【Java集合篇】HashMap的get方法是如何实现的?

HashMap的get方法是如何实现的 ✔️典型解析✔️拓展知识仓✔️如何避免HashMap get方法的哈希重✔️HashMap get方法的优缺点有哪些✔️HashMap get方法的是线程安全的吗✔️什么是ConcurrentHashMap✔️ConcurrentHashMap有哪些应用场景✔️ConcurrentHashMap的优缺点 ✔️源…

Day1Qt

1、实现登录窗口界面 头文件 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QIcon>//图标 #include <QLabel>//标签类 #include <QMovie>//动态类 #include <QLineEdit>//行编辑类 #include <QPushButton>…

现阶段鸿蒙开发薪资高于传统开发岗位的30%~50%

近期&#xff0c;多家互联网公司发布了多个和鸿蒙系统有关的岗位。 11月10日&#xff0c;网易更新了高级/资深Android开发工程师岗位&#xff0c;职位要求参与云音乐多端多os的产品&#xff08;Android、鸿蒙等&#xff09;研发迭代。11月8日&#xff0c;美团发布了鸿蒙高级工…

有没有比较好的制造业工单管理系统?

制造业公司由于要处理大量的售前售后工作&#xff0c;常常会使用不同的管理系统来协助管理&#xff0c;比如客户管理用的crm系统&#xff0c;人事管理的HR系统&#xff0c;设备管理和报修管理的工单系统等等。不同类型的系统&#xff0c;都有做得比较好的行业佼佼者&#xff0c…

Matplotlib实战_HM数据可视化

文章目录 一、先前准备1.导入必备工具包2.读取数据 二、Articles数据1.打印查看前5行数据2.查看部分字段频次统计3.制作云图 三、Customers数据1.打印前5行数据2.查看客户年龄分布图3.去重查看会员俱乐部状态4.打印查看该列数据5.查看会员俱乐部状态数量&#xff0c;绘制条形图…

华为三层交换机通 过VLANIF虚拟接口实现跨VLAN通信

S1配置 vlan batch 2 to 3interface Vlanif2ip address 192.168.2.254 255.255.255.0interface Vlanif3ip address 192.168.3.254 255.255.255.0interface GigabitEthernet0/0/2port link-type accessport default vlan 2interface GigabitEthernet0/0/3port link-type access…

听GPT 讲Rust源代码--compiler(31)

File: rust/compiler/rustc_ast_passes/src/node_count.rs 在Rust源代码的rust/compiler/rustc_ast_passes/src/node_count.rs文件中&#xff0c;它定义了Rust编译器中的AST节点计数器。该文件的作用是统计不同类型的AST节点在程序中的数量&#xff0c;以便在优化和调试过程中能…

第二十七周:文献阅读笔记

第二十七周&#xff1a;文献阅读笔记 摘要AbstractDenseNet 网络1. 文献摘要2. 引言3. ResNets4. Dense Block5. Pooling layers6. Implementation Details7. Experiments8. Feature Reuse9. 代码实现 总结 摘要 DenseNet&#xff08;密集连接网络&#xff09;是一种深度学习神…

Vue3.x+Echarts (可视化界面)

Vue3.0Echarts &#xff08;可视化界面&#xff09; 1. 简介1.1 技术选型1.2 ECharts支持的数据格式1.3 ECharts使用步骤 2. ECharts图形2.1 通用配置2.2 柱状图2.3 折线图2.4 散点图2.5 直角坐标系常用配置2.6 饼图2.7 地图2.8 雷达图2.9 仪表盘2.10 小结 3. Vue3.2ECharts5数…

利用Python实现每日新闻早报推送

本文将介绍如何使用Python编写简单的逻辑&#xff0c;通过调用API接口实现每日新闻推送功能。 步骤&#xff1a; 导入所需的库&#xff1a; 在代码的开头&#xff0c;我们需要导入所需的库。通常&#xff0c;我们会使用requests库来发送HTTP请求&#xff0c;以获取新闻数据。 …

2023三星齐发,博客之星、华为OD、Java学习星球

大家好&#xff0c;我是哪吒。 一、回顾2023 2023年&#xff0c;华为OD成了我的主旋律&#xff0c;一共发布了561篇文章&#xff0c;其中包含 368篇华为OD机试的文章&#xff1b;100篇Java基础的文章40多篇MongoDB、Redis的文章&#xff1b;30多篇数据库的文章&#xff1b;2…

Android Canvas图层saveLayer剪切clipPath原图addCircle绘制对应圆形区域,Kotlin(2)

Android Canvas图层saveLayer剪切clipPath原图addCircle绘制对应圆形区域&#xff0c;Kotlin&#xff08;2&#xff09; 在 Android Canvas图层saveLayer剪切clipRect原图对应Rect区域&#xff0c;Kotlin&#xff08;1&#xff09;-CSDN博客 的基础上&#xff0c;把矩形切图&a…

【Project】TPC-Online Module (manuscript_2024-01-07)

PRD正文 一、概述 本模块实现隧道点云数据的线上汇总和可视化。用户可以通过注册和登录功能进行身份验证&#xff0c;然后上传原始隧道点云数据和经过处理的数据到后台服务器。该模块提供数据查询、筛选和可视化等操作&#xff0c;同时支持对指定里程的分段显示和点云颜色更改…

并发(13)

目录 91.BlockQueue实现例子&#xff1f; 92.什么是BlockingDequeue?适合用在什么样的场景&#xff1f; 93.BlockingDeque与BlockingQueue有何关系&#xff0c;请对比下他们的方法&#xff1f; 94.BlockingDeque大家族有哪些&#xff1f; 96.FutureTask用来解决什么问题的…

JNPF低代码体验情况

目录 可视化拖拽搭建 平台功能特征 01、高性能、高拓展 02、满足通用场景 03、私有化部署 04、多种数据库 05、项目部署简单 06、平台全源码合作 最后 分享下引迈信息的 JNPF 吧&#xff0c;面向研发人员开发使用、100%源码、前后端分离的低代码&#xff1a; JNPF主打…

2024最新外贸建站:ChemiCloud主机购买使用及自建外贸独立站教程

随着电商平台竞争的加剧&#xff0c;许多外贸从业者意识到减少对平台依赖的重要性&#xff0c;并选择搭建自己的外贸独立站来获得更多的控制权和灵活性。即使是没有建站基础的新手&#xff0c;也可以通过学习建站来实现这一目标。下面是一个适用于新手的外贸建站教程&#xff0…

MYSQL篇--索引高频面试题

mysql索引 1什么是索引&#xff1f; 索引说白了就是一种数据结构&#xff0c;可以协助快速查询数据&#xff0c;以及更新数据库表中的数据&#xff0c;更通俗的来说索引其实就是目录&#xff0c;通过对数据建立索引形成目录&#xff0c;便于去查询数据&#xff0c;而mysql索引…