滑动窗口系列4-Leetcode322题零钱兑换-限制张数-暴力递归到动态规划再到滑动窗口

news2024/9/26 5:20:12

这个题目是Leecode322的变种,322原题如下:

我们这里的变化是把硬币变成可以重复的,并且只有coins数组中给出的这么多的金币,也就是说有数量限制: 

package dataStructure.leecode.practice;

import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;

public class CoinsChange {
    public static int coinChange(int[] coins, int amount) {
        if(amount == 0) return 0;
        int nums = process(coins, 0, amount);
        return nums == Integer.MAX_VALUE? -1 : nums;
    }

    /**
     * 最傻的递归
     * @param coins
     * @param curIndex
     * @param restAmount
     * @return
     */
    public static int process(int[] coins, int curIndex, int restAmount) {
        if(restAmount == 0) return 0;
        if(curIndex == coins.length) return Integer.MAX_VALUE;
        //两种可能性:用当前位置的数字或者不用当前位置的数字
        int p1 = process(coins, curIndex + 1, restAmount);
        int ans = p1;
        int p2 = process(coins, curIndex + 1, restAmount - coins[curIndex]);
        if(p2 != Integer.MAX_VALUE) {
            ans = Math.min(ans, p2 + 1);
        }
        return ans;
    }

    /**
     * 最傻的递归改的动态规划
     * @param coins
     * @return
     */
    public static int coinChangeDp(int[] coins, int amount) {
        int N = coins.length;
        int[][] dp = new int[N + 1][amount + 1];
        for(int j = 1; j <= amount; j++) {
            dp[N][j] = Integer.MAX_VALUE;
        }
        for(int curIndex = N - 1; curIndex >= 0; curIndex --) {
            for(int restAmount = 0; restAmount <= amount; restAmount ++) {
                //两种可能性:用当前位置的数字或者不用当前位置的数字
                int p1 = dp[curIndex + 1][restAmount];
                int ans = p1;
                if(restAmount - coins[curIndex] >= 0) {
                    int p2 = dp[curIndex + 1][restAmount - coins[curIndex]];
                    if(p2 != Integer.MAX_VALUE) {
                        ans = Math.min(ans, p2 + 1);
                    }
                }

                dp[curIndex][restAmount] = ans;
            }
        }

        return dp[0][amount];
    }


    public static int coinChange2(int[] coins, int amount) {
        if(amount == 0) return 0;

        CoinsInfo coinsInfo = getCoinsInfo(coins);
        int nums = process2(coinsInfo.values, coinsInfo.nums, 0, amount);
        return nums == Integer.MAX_VALUE? -1 : nums;
    }

    public static CoinsInfo getCoinsInfo(int[] coins) {
        //使用HashMap做频次的统计
        HashMap<Integer, Integer> count = new HashMap<>();
        //Arrays.sort(coins);
        //遍历coins的每个硬币并进行统计
        for (int coin : coins) {
            if(count.containsKey(coin)) {
                count.put(coin,count.get(coin) + 1);
            } else {
                count.put(coin, 1);
            }
        }
        //初始化values和nums数组,分别表示金额和数量,二者长度相等且一一对应
        int[] values = new int[count.size()];
        int[] nums = new int[count.size()];
        //根据count进行填充,从0下标开始填充
        int curIndex = 0;
        for (Integer value : count.keySet()) {
            //当前下标的value和num设置
            values[curIndex] = value;
            //这里要++,这样下次循环就可以进行下一个面值的统计了
            nums[curIndex ++] = count.get(value);
        }
        return new CoinsInfo(values, nums);
    }

    /**
     * 改进的递归-把coins按照面值进行分类,如果有很多重复的可以大大提高效率
     * @param values
     * @param nums
     * @param curIndex
     * @param restAmount
     * @return
     */
    public static int process2(int[] values, int[] nums, int curIndex, int restAmount) {
        if(restAmount == 0) return 0;
        if(curIndex == values.length) return Integer.MAX_VALUE;
        //当前面值的钱可以用0个到nums[curIndex]个
        int ans = process2(values, nums, curIndex + 1, restAmount);
        for(int num = 1; num <= nums[curIndex]; num ++) {
            int nextMin = process2(values, nums, curIndex + 1, restAmount - num *values[curIndex]);
            if(nextMin != Integer.MAX_VALUE) {
                ans = Math.min(ans, nextMin) + num;
            }
        }
        return ans;
    }

    public static int coinChangeDp2(int[] coins, int amount) {
        if(amount == 0) return 0;

        CoinsInfo coinsInfo = getCoinsInfo(coins);
        int[] values = coinsInfo.values;
        int[] nums = coinsInfo.nums;
        int N = values.length;
        //动态规划数组
        int[][] dp = new int[N + 1][amount + 1];
        /*if(restAmount == 0) return 0;
        if(curIndex == values.length) return Integer.MAX_VALUE;*/
        //根据递归中的上面两个if初始化动态规划数组, int默认是0所以第一个if忽略
        for(int j = 1; j <= amount; j++) {
            dp[N][j] = Integer.MAX_VALUE;
        }

        //对于普遍位置进行赋值
        for(int curIndex = N - 1; curIndex >= 0; curIndex --) {
            for(int restAmount = 0; restAmount <= amount; restAmount ++) {
                //当前面值的钱可以用0个到nums[curIndex]个
                int ans = dp[curIndex + 1][restAmount];
                for(int num = 1; num <= nums[curIndex]; num ++) {
                    int nextMin = dp[curIndex + 1][restAmount - num *values[curIndex]];
                    if(nextMin != Integer.MAX_VALUE) {
                        ans = Math.min(ans, nextMin) + num;
                    }
                }
                //斜率优化的相关分析,假设values数组为[1,2,3,5] nums为[4,2,2,1], amount = 15;
                //那我们推算dp[1][8] = dp[2][8] dp[2][6] + 1 dp[2][4] + 2中取最小值
                //而dp[1][6] = dp[2][6] dp[2][4] + 1 dp[2][2] + 2中的最小值
                //此时的dp[1][8]并不能根据dp[1][6]和dp[2][8]计算出,因为dp[1][6]用到的dp[2][2]+2在dp[1][8]中并没有,而这个可能就是最小值
            }
        }

        int result = dp[0] [amount];
        return result == Integer.MAX_VALUE? -1 : result;
    }

    public static int coinChangeSlideWindow(int[] coins, int aim) {
        if(aim == 0) return 0;

        CoinsInfo coinsInfo = getCoinsInfo(coins);
        int[] values = coinsInfo.values;
        int[] nums = coinsInfo.nums;
        int N = values.length;
        //动态规划数组
        int[][] dp = new int[N + 1][aim + 1];
        /*if(restAmount == 0) return 0;
        if(curIndex == values.length) return Integer.MAX_VALUE;*/
        //根据递归中的上面两个if初始化动态规划数组, int默认是0所以第一个if忽略
        for(int j = 1; j <= aim; j++) {
            dp[N][j] = Integer.MAX_VALUE;
        }


        //对于普遍位置进行赋值
        for(int curIndex = N - 1; curIndex >= 0; curIndex --) {
            //使用窗口内最小值的更新结构,对于每个面值的货币尝试的方位是0~(目标金额和货币金额达的最小值-1)
            //比如如果面值是30,而目标值是100,那我们进行以下的尝试:(0 30 60 90) (1 31, 61, 91) ...(29, 59, 89)
            //而对于面值100,目标值30,我们进行:(0) (1)...(29)这些尝试
            //mod代表我们当前准备尝试多少组,例如上面的面值是30,而目标值是100,我们mod就从0到29,一共30组
            for(int mod = 0; mod < Math.min(aim+1, coins[curIndex]); mod ++) {
                //创建一个最小值窗口,放的是当前的金额
                LinkedList<Integer> window = new LinkedList<>();
                //mod是当前组的第一个位置,先进去,暂时是窗口最小值
                window.add(mod);
                //先把dp[curIndex][mod]的初始值设置为dp[curIndex + 1][mod]
                dp[curIndex][mod] = dp[curIndex + 1][mod];
                //在小于目标金额的情况下,每次加当前面值,类似0 30 60 90,不超过目标金额
                for(int curAmount = mod + coins[curIndex]; curAmount <= aim; curAmount += coins[curIndex]) {
                    //如果窗口不为空且窗口最后的数是Integer的最大值或者最后的值+补偿金额大于当前的数量(下一行的dp值)
                    while(!window.isEmpty() && (dp[curIndex+1][window.peekLast()] == Integer.MAX_VALUE || window.peekLast() + composite(window.peekLast(), curAmount, coins[curIndex]) >= dp[curIndex + 1][curAmount])) {
                        window.pollLast();
                    }
                    //把当前金额(dp里的列)
                    window.addLast(curAmount);
                    //当前钱数-货币金额*货币数量刚好是可用的,如果再加一个就不行了,计算当前值或者计算下一个值的时候就都不能用了
                    int overdue = curAmount - coins[curIndex] * (nums[curIndex] + 1);
                    if(window.peekFirst() == overdue) {
                        window.pollFirst();
                    }

                    dp[curIndex][curAmount] = dp[curIndex + 1][window.peekFirst()] + composite(window.peekFirst(), curAmount, coins[curIndex]);
                }
                //斜率优化的相关分析,假设values数组为[1,2,3,5] nums为[4,2,2,1], amount = 15;
                //那我们推算dp[1][8] = dp[2][8] dp[2][6] + 1 dp[2][4] + 2中取最小值
                //而dp[1][6] = dp[2][6] dp[2][4] + 1 dp[2][2] + 2中的最小值
                //此时的dp[1][8]并不能根据dp[1][6]和dp[2][8]计算出,因为dp[1][6]用到的dp[2][2]+2在dp[1][8]中并没有,而这个可能就是最小值
            }
        }

        int result = dp[0] [aim];
        return result == Integer.MAX_VALUE? -1 : result;
    }

    private static int composite(int preAmount, int curAmount, int coinAmount) {
        return (curAmount - preAmount)/ coinAmount;
    }


    public static void main(String[] args) {
        int[] coins = {2,5,3,2,5,2};
        int amount = 15;
        int nums = coinChange(coins, amount);
        System.out.println(nums);
        int numsDp = coinChangeDp(coins, amount);
        System.out.println(numsDp);

        int nums2 = coinChange2(coins, amount);
        System.out.println(nums2);

        int numsDp2 = coinChange2(coins, amount);
        System.out.println(numsDp2);

        int numsWindow = coinChangeSlideWindow(coins, amount);
        System.out.println(numsWindow);
    }
}

class CoinsInfo {
    int[] values;
    int[] nums;

    public CoinsInfo(int[] values, int[] nums) {
        this.values = values;
        this.nums = nums;
    }
}

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

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

相关文章

ChatGPT癌症治疗“困难重重”,真假混讲难辨真假,准确有待提高

近年来&#xff0c;人工智能在医疗领域的应用逐渐增多&#xff0c;其中自然语言处理模型如ChatGPT在提供医疗建议和信息方面引起了广泛关注。然而&#xff0c;最新的研究表明&#xff0c;尽管ChatGPT在许多领域取得了成功&#xff0c;但它在癌症治疗方案上的准确性仍有待提高。…

华为OD机试 - 完全数计算(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、Java算法源码五、效果展示六、纵览全局 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》。 刷的越多&…

Lalamu-免费视频口型同步工具,创建属于你自己的虚拟数字人

什么是Lalamu? Lalamu 是一款视频口型同步应用程序。该应用程序由 Lalamu Entertainment 开发&#xff0c;允许用户对视频中的任何面孔进行口型同步。无论是人物、人体模型、书籍封面、艺术品、演员、婴儿、蜡像&#xff0c;甚至银行账单上的面孔&#xff0c;Lalamu 都可以让…

FreeSWITCH 1.10.10 简单图形化界面4 - 腾讯云NAT设置

FreeSWITCH 1.10.10 简单图形化界面4 - 腾讯云NAT设置 0、 界面预览1、 查看IP地址2、 修改协议配置3、 开放腾讯云防火墙4、 设置ACL5、 设置协议中ACL&#xff0c;让PBX匹配内外网6、 重新加载SIP模块7、 查看状态8、 测试一下 0、 界面预览 http://myfs.f3322.net:8020/ 用…

尝试自主打造一个有限状态机(二)

前言 上一篇文章我们从理论角度去探索了状态机的定义、组成、作用以及设计&#xff0c;对状态机有了一个基本的认识&#xff0c;这么做有利于我们更好地去分析基于实际应用的状态机&#xff0c;以及在自主设计状态机时也能更加地有条不紊。本篇文章将从状态机的实际应用出发&am…

AI项目四:基于mediapipe的钢铁侠操作(虚拟拖拽)

若该文为原创文章&#xff0c;转载请注明原文出处。 一、介绍 在B站看到使用CVZone进行虚拟物体拖放&#xff0c;模仿钢铁侠电影里的程序操作&#xff01;_哔哩哔哩_bilibili 是使用CVZone方式实现的&#xff0c;代码教程来源https://www.computervision.zone,感兴趣可以测试…

MathType7.4mac最新版本数学公式编辑器安装教程

MathType7.4中文版是一款功能强大且易于使用的公式编辑器。该软件可与word软件配合使用&#xff0c;有效提高了教学人员的工作效率&#xff0c;避免了一些数学符号和公式无法在word中输入的麻烦。新版MathType7.4启用了全新的LOGO&#xff0c;带来了更多对数学符号和公式的支持…

全国自考02325《计算机系统结构》历年真题试卷及答案 年份:202304

2023 年4 月高等教育自学考试全国统一命题考试 计算机系统结构试题答案及评分参考 &#xff08;课程代码 02325) 一、选择题&#xff1a;本大题共 10小题&#xff0c;每小题1分 1.D 2.B 3.D 4.A 5.B 6.C 7.D 8.D 9. A 10.C 二、填空题&#xff1a;本大题共 10小题&#xff…

如何加入微信群?微信加群教程,如何加更多的群聊?

微信群是人们交流互动的重要平台之一&#xff0c;加入合适的微信群可以拓宽人脉、获取信息和分享经验。下面小编将分享三个方法&#xff0c;帮助你轻松加入心仪的微信群。 方法一&#xff1a;扫描二维码加入 很多微信群会设置专属的二维码&#xff0c;方便他人加入。当你得到某…

Linux操作系统--shell编程(条件判断)

(1).基本的语法 test condition [ condition ] 注意condition前后要有空格;在使用该种表达式的时候,条件非空即为 true,[ hello ]返回 true,[ ] 返回 false。我们可以通过echo $?来判断上一次执行的情况来判断真假(0真1假)。

详解预处理

全文目录 前言预定义符号#define 定义标识符常量#define 定义宏#define 替换规则# 宏参数转换字符串## 宏参数拼接带有副作用的宏参数 宏与函数的对比#undef 移出宏定义命令行定义条件编译#include 文件包含头文件的包含方式头文件的重复包含 前言 前面我们学习了程序的编译和…

【C语言基础】源文件与头文件详解

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

Office Tool Plus 使用与激活

Office Tool Plus 一个强大且实用的 Office 部署工具。 可以免费激活使用office各种版本 体验了一下&#xff0c;觉得很不错&#xff0c;我介绍一下我使用的方式&#xff1a; 1.安装软件 访问官网&#xff1a;Office Tool Plus 选择ZIP的包&#xff0c;下载后解压&#xff0c…

Huggingface托管机器学习模型及API提供

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 我想在我的网络和移动应用程序中使用机器学习模型&#xff0c;但要做到这一点&#xff0c;我必须在某个地方托管我的机器学习应用程序。 托管预先训练的 ML 模型称为推理。 我只想添加一些 Python ML 代码并快速获得 REST…

一段简单的汇编语言源程序【2】

此文章主要记录代码的编写&#xff0c;编译&#xff0c;连接&#xff0c;调试过程&#xff0c;相关工具的安装和使用介绍在前面的文章中已提供。 主要功能通过栈实现两个数的交换 源代码如下&#xff1a; assume cs:codesg codesg segmentmov ax,2000Hmov ss,axmov sp,0add s…

国产发力,长存入局|相变存储器PCM是SCM的未来吗?

在去年7月份有一件震惊存储圈的事情&#xff0c;那就是Intel说要放弃Optane产品线&#xff0c;包括PMEM和SSD两个方向都要放弃。存储圈看到听到这个消息也是一脸的茫然。 在Optane产品发布之前&#xff0c;大家针对DRAM和SSD之间的性能gap一直在苦苦找寻合适的产品。SCM存储级内…

深度学习12:胶囊神经网络

目录 研究动机 CNN的缺陷 逆图形法 胶囊网络优点 胶囊网络缺点 研究内容 胶囊是什么 囊间动态路由算法 整体框架 编码器 损失函数 解码器 传统CNN存在着缺陷&#xff08;下面会详细说明&#xff09;&#xff0c;如何解决CNN的不足&#xff0c;Hinton提出了一种对于图…

一篇掌握BFD技术(二):OSPF与BFD联动配置

1. 实验目的 熟悉OSPF与BFD联动的应用场景掌握OSPF与BFD联动的配置方法 想要华为数通配套实验拓扑和配置笔记的朋友们点赞关注&#xff0c;评论区留下邮箱发给你&#xff01; 2. 实验拓扑 3. 实验步骤 1&#xff09;IP地址的配置 AR1的配置 <Huawei>system-view…

Origin软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Origin是一款专业的科学绘图和数据分析软件&#xff0c;由美国OriginLab公司开发。它提供了丰富的数据分析和绘图工具&#xff0c;适用于各种科学领域&#xff0c;如生物学、化学、物理学、医学、地球科学等。Origin软件的主要特…

使用 S3 生命周期精确管理对象生命周期

在亚马逊工作这些年,我发现 S3 的生命周期配置是管理对象生命周期的重要但复杂的工具。在这篇文章中,我将利用实战经验,深入剖析生命周期,从核心概念到实际应用。 亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏、培训视频、活…