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

这个题目是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;
    }
}

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

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

相关文章

金融风控数据分析-信用评分卡建模(附数据集下载地址)

本文引用自&#xff1a; 金融风控&#xff1a;信用评分卡建模流程 - 知乎 (zhihu.com) 在原文的基础上加上了一部分自己的理解&#xff0c;转载在CSDN上作为保留记录。 本文涉及到的数据集可直接从天池上面下载&#xff1a; Give Me Some Credit给我一些荣誉_数据集-阿里云…

Docker搭建私有仓库并迁移

目录 方案 A、B机器安装docker 设置阿里云镜像源 安装 Docker-CE并设置为开机自动启动 A机器准备数据 拷贝数据 B机器运行redis、mysql镜像 重启docker服务 方案 准备两台机器&#xff1a;A机器&#xff08;可以连接外网&#xff09;&#xff0c;B机器&#xff08;内网机器…

python web GUI框架-NiceGUI 教程(一)

python web GUI框架-NiceGUI 教程&#xff08;一&#xff09; streamlit可以在一些简单的场景下仍然推荐使用&#xff0c;但是streamlit实在不灵活&#xff0c;受限于它的核心机制&#xff0c;NiceGUI是一个灵活的web框架&#xff0c;可以做web网站也可以打包成独立的exe。 基…

Docker 将容器打包成镜像推送镜像到仓库

Docker 将容器打包成镜像&推送镜像到仓库 一、将容器打包成镜像 $ docker commit <容器ID> <镜像名称:标签>示例&#xff1a; $ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS …

字节跳动推出AI对话工具“豆包”:免费用

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 听说松松客服的小马爆料了一个消息&#xff1a;字节跳动推出了一个新的AI大模型对话工具&#xff0c;叫做“豆包”。竟然一查发现&#xff0c;早在8月18号就已经上线了呢。原来这个“豆包”其实是之…

Kind创建本地环境安装Ingress

目录 1.K8s什么要使用Ingress 2.在本地K8s集群安装Nginx Ingress controller 2.1.使用Kind创建本地集群 2.1.1.创建kind配置文件 2.1.2.执行创建命令 2.2.找到和当前k8s版本匹配的Ingress版本 2.2.1.查看当前的K8s版本 2.2.2.在官网中找到对应的合适版本 2.3.按照版本安…

day30 日期转换

一&#xff1a;Date Date类&#xff1a; 这个类是java.util.Date getTime() : 获取内部维护的long值 Date date new Date(); long time date.getTime(); setTime()&#xff1a;按照指定的long值&#xff08;表示的时间&#xff09;设置Date表示的时间 time 60*60*24*1000;…

Android学习之路(11) ActionBar与ToolBar的使用

自android5.0开始&#xff0c;AppCompatActivity代替ActionBarActivity&#xff0c;而且ToolBar也代替了ActionBar&#xff0c;下面就是ActionBar和ToolBar的使用 ActionBar 1、截图 2、使用 2.1、AppCompatActivity和其对应的Theme AppCompatActivity使用的是v7的ActionBa…

快乐开源活动全面升级!提PR,赢PS5、Switch等缤纷好礼

快乐开源 活动升级 礼品升级 PS5、Switch、Apple、雷蛇、富士…… 开发者们想要的 我们安排&#xff01; 飞桨快乐开源活动旨在鼓励更多的开发者参与到飞桨社区的开源建设中&#xff0c;帮助修复 bug 或贡献 feature。活动起初只是一个「提 PR 领取新年礼物 &#x1f381;」…

python简介

Python 是一门优雅而健壮的编程语言&#xff0c;它继承了传统编译语言的强大性和通用性&#xff0c;同时也借鉴了脚本语言和解释语言的易用性。 Python 的历史 Python是由创始人贵铎范罗萨姆&#xff08;Guido van Rossum&#xff09;在阿姆斯特丹于1989年圣诞节期间&#xf…

多张图片转为pdf怎么弄?

多张图片转为pdf怎么弄&#xff1f;在网络传输过程中&#xff0c;为了避免图片格式文件出现差错&#xff0c;并确保图片的清晰度和色彩不因不同设备而有所改变&#xff0c;常见的做法是将图片转换为PDF格式。然而&#xff0c;当涉及到多张图片时&#xff0c;逐一转换将会变得相…

PyCharm切换虚拟环境

PyCharm切换虚拟环境 为了满足不同任务需要不同版本的包&#xff0c;可以在Anaconda或者Miniconda创建多个虚拟环境文件夹&#xff0c;并在PyCharm下切换虚拟环境。 解决方案 1、打开Ananconda Prompt 2、创建自己的虚拟环境 格式&#xff1a;conda create -n 虚拟环境名字…

虚拟机的使用

首先需要安装VMware软件&#xff0c;这是虚拟机&#xff0c;在里面可以实现在windows的笔记本上运行包括&#xff0c;windows11和linux系统的开发和研究。 VMware是一种虚拟化技术&#xff0c;可以让你在一台物理计算机上运行多个操作系统和应用程序&#xff0c;而不需要重启或…

如何在,Linux中安装Luajit2.*

1.文件下载The LuaJIT Project 2.将下载文件上传到对应的服务器&#xff1a;例如/opt 3.进入对应的文件夹 4.make PREFIX/usr/local&#xff0c;设置安装路径 5.make install&#xff0c;编译安装 6.进入安装目录&#xff0c;cd /usr/local/include/luajit-2.0 7.luajit -v…

目标检测笔记(十二):如何通过界面化操作YOLOv5完成数据集的自动标注

文章目录 一、意义二、修改源码获取三、自动标注前期准备四、开始自动标注五、可视化标注效果六、XML转换TXT 一、意义 通过界面化操作YOLOv5完成数据集的自动标注的意义在于简化数据标注的流程&#xff0c;提高标注的效率和准确性。 传统的数据集标注通常需要手动绘制边界框…

第三届计算机、物联网与控制工程国际学术会议(CITCE 2023)

第三届计算机、物联网与控制工程国际学术会议&#xff08;CITCE 2023) The 3rd International Conference on Computer, Internet of Things and Control Engineering&#xff08;CITCE 2023) 第三届计算机、物联网与控制工程国际学术会议&#xff08;CITCE 2023&#xff09;…

ChatGPT在医疗领域可应用于改善与患者的沟通

注意&#xff1a;本信息仅供参考&#xff0c;发布该内容旨在传递更多信息的目的&#xff0c;并不意味着赞同其观点或证实其说法。 自从ChatGPT在2022年末对公众开放以来&#xff0c;OpenAI的这款生成式AI聊天机器人在医疗领域展示出了巨大潜力。它已经通过了美国医学执照考试&a…

NameError: name ‘_mysql‘ is not defined

报错信息 Traceback (most recent call last):File "/Users/xuruilong/Desktop/cmabc_back/.enve/lib/python3.9/site-packages/MySQLdb/__init__.py", line 18, in <module>from . import _mysql ImportError: dlopen(/Users/xuruilong/Desktop/cmabc_back/.…

无涯教程-Android - Intents/Filters

Android Intent 是要执行的操作的抽象描述。它可以与 startActivity 一起启动Activity&#xff0c;将 broadcastIntent 发送给任何BroadcastReceiver组件&#xff0c;并与 startService(Intent)或 bindService(Intent&#xff0c;ServiceConnection&#xff0c;int)与后台服务进…

Linux驱动——Tiny4412芯片_Source Insight的下载+Linux3.5内核下工程的创建

文章目录 前言Source Insight的下载1.下载地址2.下载步骤 linux3.5内核下工程的创建 前言 本博客仅作为笔记总结&#xff0c;以及帮助有需要的人&#xff0c;不作权威解释。 Source Insight的下载 1.下载地址 官网&#xff1a;https://www.sourceinsight.com/ 另外可以选择…