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

本算法是在算法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进行研究,看看此题是否还可以对空间复杂度进行优化

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机缺失vcomp120.dll文件怎么办?总结多种解决方法分享

在使用电脑过程中&#xff0c;难免会遇到各种问题&#xff0c;其中vcomp120.dll丢失问题就是其中之一。这个问题可能会给用户带来诸多不便&#xff0c;导致某些应用程序无法正常运行。在这篇文章中&#xff0c;我们将详细介绍vcomp120.dll文件的重要性&#xff0c;以及遇到丢失…

使用 vue-json-viewer 工具在界面显示json格式数据

安装vue-json-viewer npm install vue-json-viewer --save 引入&#xff1a; import JsonViewer from vue-json-viewer Vue.use(JsonViewer) 使用&#xff1a; <json-viewer :value"jsonData" show-double-quotes :preview-mode"true" :show-array…

存储器进化全解析:从NAND到UFS,深入剖析常见存储技术与应用

存储领域发展至今&#xff0c;已有很多不同种类的存储器产品。下面给大家介绍几款常见的存储器及其应用&#xff1a;#存储器#​ 一、NAND NAND Flash存储器是Flash存储器的一种&#xff0c;属于非易失性存储器&#xff0c;其内部采用非线性宏单元模式&#xff0c;为固态大容量…

mmdetection训练自己的数据集

mmdetection训练自己的数据集 这里写目录标题 mmdetection训练自己的数据集一&#xff1a; 环境搭建二&#xff1a;数据集格式转换(yolo转coco格式)yolo数据集格式coco数据集格式yolo转coco数据集格式yolo转coco数据集格式的代码 三&#xff1a; 训练dataset数据文件配置config…

C#,迭代深化搜索(IDS)或迭代深化深度优先搜索(IDDFS)算法的源代码

摘要&#xff1a;本文介绍适合于大数据规模情况下的&#xff0c;新型的迭代深化深度优先搜索(IDDFS)算法的原理、实例及实现的C#源代码。 引言 常用的树&#xff08;或图&#xff09;遍历算法是两种&#xff1a; 广度优先搜索算法&#xff08;BFS&#xff09; 和 深度优先搜索…

C#编程-实现文件输入和输出操作

实现文件输入和输出操作 所有程序接受用户的输入、处理输入并且产生输出。所以,所有的编程语言都支持输入和输出操作。例如,您需要为教师开发程序以接受学生的结果信息。您的程序应该将信息保存在硬盘的Result.xls文件中。您可以在程序中使用文件输入和输出操作以接受来自教…

外汇网站主要业务逻辑梳理

上图为工行ICBC的外汇保证金交易界面。 当需要买入帐户欧元&#xff08;欧元人民币&#xff09;时&#xff0c;买入100欧元&#xff0c;因为没有杠杆&#xff0c;虽然欧元中间价是782.34&#xff0c;但实际需要支付783.14元人民币的保证金&#xff0c;这个兑换不是真实的外汇兑…

全网独家:基于openeuler-20.03-lts底包构建opengauss数据库V5.0.1LTS的单机容器

近期想测试一下opengauss数据库,官网上单机容器部署只有x86-64平台CentOS 7.6和ARM64平台 openEuler20.03 LTS两种底包方案。本文系全网独家在x86平台上基于openeuler-20.03-lts底包构建opengauss数据库V5.0.1LTS的单机容器。 opengauss官网上单机容器部署只有x86-64平台Cent…

计算机毕业设计 基于javaweb的学生交流培养管理平台/系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

910b上跑Chatglm3-6b进行流式输出【pytorch框架】

文章目录 准备阶段避坑阶段添加代码结果展示 准备阶段 配套软件包Ascend-cann-toolkit和Ascend-cann-nnae适配昇腾的Pytorch适配昇腾的Torchvision Adapter下载ChatGLM3代码下载chatglm3-6b模型&#xff0c;或在modelscope里下载 避坑阶段 每个人的服务器都不一样&#xff0…

Unity3d 实现直播功能(无需sdk接入)

Unity3d 实现直播功能 需要插件 :VideoCapture 插件地址(免费的就行) 原理:客户端通过 VideoCapture 插件实现推流nodejs视频流转服务进行转发,播放器实现rtmp拉流 废话不多说,直接上 CaptureSource我选择的是屏幕录制,也可以是其他源 CaptureType选择LIVE–直播形式 LiveSt…

IDEA[Debug]简单说明

目录 &#x1f95e;1.打断点 &#x1f32d;2.第一组按钮 &#x1f9c2;3.第二组按钮 &#x1f953;4.参数查看 1.打断点 1.在需要断点处打上断点&#xff0c;然后点击debug运行 2.执行debug&#xff0c;直接执行到断点处 2.第一组按钮 共有8按钮&#xff0c;从左往右依…

【系统高级-环境变量】path配置一整行,而不是列表

这是列表编辑方便。但是不知道为什么变成一行&#xff0c;非常的令人抓狂&#xff0c;经过研究发现&#xff0c;第一个环境变量必须为C:\Windows\system32 开头才可以 文章如下 修改环境变量中的一行变成列表形式_环境变量编辑不是列表-CSDN博客

回归预测 | Matlab实现RIME-HKELM霜冰算法优化混合核极限学习机多变量回归预测

回归预测 | Matlab实现RIME-HKELM霜冰算法优化混合核极限学习机多变量回归预测 目录 回归预测 | Matlab实现RIME-HKELM霜冰算法优化混合核极限学习机多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现RIME-HKELM霜冰算法优化混合核极限学习机多变…

苍穹外卖Day01——总结1

总结1 1. 软件开发整体介绍1.1 软件开发流程1.2 角色分工1.3 软件环境 2. 苍穹外卖项目介绍2.1 项目介绍2.2 技术选项 3. Swagger4. 补充内容&#xff08;待解决...&#xff09; 1. 软件开发整体介绍 1.1 软件开发流程 1.2 角色分工 从角色分工里面就可以查看自己以后从事哪一…

芯片命名大全:完整的器件型号包括主体型号、前缀、后缀等!

不少公司的采购会发现&#xff0c;拿到工程师提供的BOM中的器件去采购物料时&#xff0c;经常供应商还会问得更仔细&#xff0c;否则就不知道供给你哪种物料&#xff0c;严重时&#xff0c;采购回来的物料用不了。为什么会有这种情况呢&#xff1f;问题就在于&#xff0c;很多经…

数据结构—图(下)

文章目录 12.图(下)(4).生成树和最小生成树#1.什么是生成树和最小生成树&#xff1f;i.生成树ii.最小生成树 #2.Prim算法i.算法思想ii.看看例子iii.代码实现 #3.Kruskal算法i.算法思想ii.看看例子iii.代码实现 #4.次小生成树 (5).最短路径问题#1.加权有向图的最短路径问题#2.单…

盘点三款服务器运维工具

随着世界变得更加数字化&#xff0c;如何便捷高效地管理服务器变得越来越重要&#xff0c;能有一款简易实用且现代化服务器管理工具就显得尤为关键。今天就选取了三款服务器运维工具进行对比分析&#xff0c;评测每款产品的优缺点。 产品清单 宝塔面板 简介&#xff1a;国内老…

工作流自动化:它是什么,常见示例以及如何实现

由于您的组织旨在留住顶尖人才和高价值客户&#xff0c;因此您需要不断为这两个团队提供一流的体验。 就客户而言&#xff0c;它可以实时解决他们的问题和疑虑&#xff0c;并以深思熟虑、可操作的洞察力主动与他们联系&#xff1b;而且&#xff0c;对于员工来说&#xff0c;它可…

推荐一款强大的AI开源项目!有了它,将你的数据库秒变AI数据库!

前言 在当今数字化的世界中&#xff0c;数据库系统扮演着至关重要的角色。而原生系统的功能我们也大都知晓&#xff0c;无非是一些增删改查、数据优化的使用。但有一些开源工具项目可以帮助我们对数据库降本增效。 在本文中&#xff0c;小编将介绍一个名为SuperDuperDB的开源…