【动态规划专题】3:换钱的最少货币数

《程序员代码面试指南--IT名企算法与数据结构题目最优解》 左程云 著

换钱的最少货币数

【题目】
给定数组arr, arr中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,
再给定一个整数aim,代表要找的钱数,求组成aim的最少货币数。

【举例】
arr=[5,2,3],aim=20
4张5元可以组成20元,其他找钱方案都要使用更多的货币,所以返回4.

arr=[5,2,3],aim=0
不用任何货币就可以组成0元,返回0

arr=[3,5],aim=2
根本没法组成2元,钱不能找开的情况下默认返回-1.

 /// 方法1:暴力递归求解

#include <algorithm>
#include <iostream>
#include <stack>
#include <vector>
#include <exception>
using namespace std;

#define INT_MAX   1000

int GetMinNum(int A, int B)
{
    return A > B ? B : A;
}

int process(int *arr,int length, int i, int rest);
////采用递归的方式实现,时间复杂度很高,不推荐,但是需要理解这里的递归思路
int minCoins1(int* arr, int length, int aim)
{
    if (arr == nullptr || length <= 0 || aim <= 0)
    {
        return -1;
    }

    return process(arr, length, 0, aim);
}

//当前考虑的面值是arr[i],还剩rest的钱需要找零
//如果返回-1,说明自由使用 arr[i...N-1]面值的情况下,无论如何也无法找零rest
//如果返回不是-1,代表自由使用arr[i...N-1]面值的情况下,找零rest需要的最少张数
int process(int *arr, int length, int i, int rest)
{
    //base case
    //已经没有面值能够考虑了
    //如果此时剩余的钱为0,返回0张
    //如果此时剩余的钱不是0,返回-1
    if (i >= length)
    {
        if (rest == 0)
        {
            cout << "====================="<<endl;
        }
        return rest == 0 ? 0 : -1;
    }

    //最少张数,初始时为-1,因为还没找到有效解
    int res = -1;
    //依次尝试使用当前面值(arr[i])0张、1张、k张,但不能超过rest
    for (int k = 0; k*arr[i] <= rest; k++)
    {
        //使用了k张aim[i],剩下的钱为 rest - k*arr[i]
        //交给剩下的面值去搞定 (arr[i+1...N-1])
        int next = process(arr, length, i + 1, rest - k*arr[i]);

        //说明这个后续过程有效
        if (next != -1)
        {
            res = res == -1 ? next + k : GetMinNum(res, next + k);
        }
    }

    cout << "res" << res << endl;
    return res;
}

将递归的思路转为循环。目前不太理解。。  递归转循环!!!

方法2:借用辅助二维数组 dp[N+1][aim+1],通过循环,由下至上,逐层求解。

void printDPArray(int* dp, int cols)
{
    for (int j = 0; j < cols; j++)
    {
        printf("%d,", (int)(*(dp + j)));
    }
    cout << endl;
}

int minCoin2(int* arr, int length, int aim)
{
    if (arr == nullptr || length <= 0 || aim < 0)
    {
        return -1;
    }
    int N = length;
    int** dp = new int*[N + 1];
    for (int i = 0; i < N+1; i++)
    {
        dp[i] = new int[aim + 1];
    }

    for (int i = 0; i < N + 1; i++)
    {
        for (int j = 0; j < aim + 1; j++)
        {
            dp[i][j] = 0;
        }
    }

    //设置最后一排的值,除dp[N][0]为0外,其他都是-1
    dp[N][0] = 0;
    for (int col = 1; col <= aim; col++)
    {
        dp[N][col] = -1;
    }

    //从底下往上计算每一行
    for (int i = N - 1; i >= 0; i--)
    {
        //每一行都从左到右
        for (int rest = 0; rest <= aim; rest++)
        {
            dp[i][rest] = -1;//初始时先设置dp[i][rest]的值无效

            //下面的值如果有效
            if (dp[i + 1][rest] != -1)
            {
                dp[i][rest] = dp[i + 1][rest];//先设置为下面的值
            }

            //如果左边的位置不越界且有效
            if (rest - arr[i] >= 0 && dp[i][rest - arr[i]] != -1)
            {
                //如果之前下面的值无效
                if (dp[i][rest] == -1)
                {
                    dp[i][rest] = dp[i][rest - arr[i]] + 1;
                }
                else
                {
                    //说明下面和左边的值都有效,去最小的
                    dp[i][rest] = GetMinNum(dp[i][rest], dp[i][rest - arr[i]] + 1);
                }
            }
        }
    }

    ////二维数组打印,有点不熟悉。。。
    for (int i = 0; i < N + 1; i++)
    {
        printDPArray(*(dp+i),aim + 1);
    }
    cout << endl;
    cout << endl;

    cout << "printDPArray====================start" << endl;
    for (int i = 0; i < N + 1; i++)
    {
        printf("Row:%d.  ", i);
        for (int j = 0; j < aim + 1; j++)
        {
            printf("%d,", dp[i][j]);
        }
        cout << endl;
    }
    cout << "printDPArray====================end" << endl;

/*
    printDPArray == == == == == == == == == == start
    Row : 0.  0, -1, 1, 1, 2, 1, 2, 2, 2, 3, 2, 3, 3, 3, 4, 3, 4, 4, 4, 5, 4,
    Row : 1.  0, -1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7,
    Row : 2.  0, -1, 1, -1, 2, -1, 3, -1, 4, -1, 5, -1, 6, -1, 7, -1, 8, -1, 9, -1, 10,
    Row : 3.  0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    printDPArray == == == == == == == == == == end
*/

    return dp[0][aim];
}

然后逐一扫描钱币,计算每种币种,组成 arr[i] ~ aim 的最少货币数

后面的最优解依靠前面的最优解,所以需要从左往右扫描。

第1行就是 arr[0] 面额的的钱币,依次组成1~aim,需要的张数

方法3:借用一个一维的数组,数组长度是 aim+1。这个比较好理解一些。

void PrintDPArr2(vector<int>& dp)
{
    for (vector<int>::iterator it1 = dp.begin(); it1 != dp.end(); ++it1)
    {
        cout << *it1<<",";
    }
    cout << endl;
}

void MiniCoin3(int* arr, int length, int aim)
{
    cout << "MiniCoin3======================" << endl;
    vector<int> dp(aim + 1, INT_MAX);
    dp[0] = 0;

    for (int i = 0; i < length; ++i)
    {
        for (int j = arr[i]; j <= aim; ++j)
        {
            if (dp[j - arr[i]] != INT_MAX)
            {
                dp[j] = min(dp[j], dp[j - arr[i]] + 1);
            }
        }
        PrintDPArr2(dp);////这里只是打印每行的临时结果,方便后期理解
    }

    if (dp[aim] == INT_MAX) cout << "-1" << endl;
    else  cout << "dp[aim] "<<dp[aim] << endl;

    /* aim=6,arr=[2,1,3]
    0,1000,1,1000,2,1000,3,
    0,1,1,2,2,3,3,
    0,1,1,1,2,2,2,
    dp[aim] 2
    */
}
//====================测试用例=================
void test1()
{
    int arrCoin1[3] = {2,1,3};
    int arrCoin2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 60, 62, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 112, 113, 114, 115, 116, 117 };
    //int arrCoin2[] = { 1,3,5,6 };

    //cout << "Test1================" << endl;
    //cout << "expected:4. " << minCoin2(arrCoin1, sizeof(arrCoin1) / sizeof(int), 6) << endl;;

    MiniCoin3(arrCoin1, sizeof(arrCoin1) / sizeof(int), 6);
    //MiniCoin3(arrCoin2, sizeof(arrCoin2) / sizeof(int), 122);

    //cout << "expected:2. " << minCoins1(arrCoin2, sizeof(arrCoin2) / sizeof(int), 122) << endl;;
}

int main()
{
    test1();

    cout << endl;
    system("pause");
    return 0;
}

原文地址:https://www.cnblogs.com/music-liang/p/12059230.html

时间: 2024-11-05 20:32:47

【动态规划专题】3:换钱的最少货币数的相关文章

[程序员代码面试指南]递归和动态规划-换钱的最少货币数

题目描述 给定arr,arr中所有的值都为正数且不重复.每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim,求组成aim的最少货币数. 解题思路 dp[i][j]表示只用第0到i种货币,凑成j元的最小货币张数. 初始化: 转移方程: dp[i][j]=min{dp[i-1][j-k*arr[i]]+k} (k>=0) 整理得 dp[i][j]=min{dp[i-1][j],min{dp[i-1][j-k*arr[i]]+k}} (k>=1) 变换得 dp[i][j]=

算法之Python实现 - 002 : 换钱的最少货币数补充(每种货币只能使用一次)

[题目]:给定数组arr,arr中所有的值都为正数且不重复.每个值代表一种面值的货币,每种面值的货币仅可以使用一张,再给定一个整数aim代表要找的钱数,求组成aim的最少货币数. [代码1]:时间与额外空间复杂度O(N*aim) import numpy as np from xmlrpc.client import MAXINT def mincoin(arr,aim): if len(arr)<0: print("No coin provided for change!")

动态规划-换钱最少货币数

#encoding:utf-8 _author_ = "Wang Wenchao" #换钱最少的货币数 #给定数组arr,arr中所有的值都为正数且不重复.每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个正数aim代表要找的钱数,求组成aim的最少货币数 ''' arr=[5,2,3],aim=20 4张5元可以组成20元,所以返回4 arr=[5,2,3],aim=0 返回0 arr=[3,5],aim=2 返回-1 ''' #解法:arr长度为N,生成行数为N的,

换钱最少货币数

给定数组arr,arr中所有的值都为正数且不重复.每个值代表一种面值的货币,每种面值的货币可以使用任意张,在给定一个整数aim代表要找的钱数,求组成aim的最少货币数. 代码: public class MinCoins { public static int minCoins(int[] arr, int aim) { if(arr == null || arr.length==0 || aim<=0) { return 0; } int len = arr.length; int max =

每日一题之动归-换钱的最少次数(一)

题目: 给定数组arr,arr中所有的值都为正数且不重复.每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求组成aim的最少货币数. 举个例子 arr[5,2,3] ,aim=20 4张5元可以组成20,并且是最小的,所以返回4 arr[5,2,3],aim=0. 不用任何货币就可以组成0元,这里返回0. arr[5,2,3],ami=4 这里无法组成返回-1 思路:这里我们采用经典的动态规划的方法做,时间复杂度可以在O(n^2). 经典动态规划的放法

1031: 最少钱币数

1031: 最少钱币数 时间限制: 1 Sec  内存限制: 128 MB提交: 156  解决: 127[提交][状态][讨论版] 题目描述 这是一个古老而又经典的问题.用给定的几种钱币凑成某个钱数,一般而言有多种方式.例如:给定了 6 种钱币面值为 2.5.10.20.50.100,用来凑 15 元,可以用 5 个 2 元.1个 5 元,或者 3 个 5 元,或者 1 个 5 元.1个 10 元,等等.显然,最少需要 2 个钱币才能凑成 15 元.        你的任务就是,给定若干个互不

最少钱币数:

最少钱币数 [问题描述] 这是一个古老而又经典的问题.用给定的几种钱币凑成某个钱数,一般而言有多种方式.例如:给定了6种钱币面值为2.5.10.20.50.100,用来凑 15元,可以用5个2元.1个5元,或者3个5元,或者1个5元.1个10元,等等.显然,最少需要2个钱币才能凑成15元. 你的任务就是,给定若干个互不相同的钱币面值,编程计算,最少需要多少个钱币才能凑成某个给出的钱数. [要求] [数据输入]输入可以有多个测试用例.每个测试用例的第一行是待凑的钱数值M(1 <= M <= 20

算法笔记——硬币找零之最少硬币数

题目来源:NYOJ995 问题描述: 在现实生活中,我们经常遇到硬币找零的问题,例如,在发工资时,财务人员就需要计算最少的找零硬币数,以便他们能从银行拿回最少的硬币数,并保证能用这些硬币发工资. 我们应该注意到,人民币的硬币系统是 100,50,20,10,5,2,1,0.5,0.2,0.1,0.05, 0.02,0.01 元,采用这些硬币我们可以对任何一个工资数用贪心算法求出其最少硬币数. 但不幸的是: 我们可能没有这样一种好的硬币系统, 因此用贪心算法不能求出最少的硬币数,甚至有些金钱总数还

最大不相交区间数+最少区间数覆盖问题 贪心思想

最大不相交区间数的一道题是hdu2037 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2037 题目给出n个区间,问最多有多少个区间没有重叠,只需要对区间右端点进行排序就行,因为一个节目结束得早的话就会为其他节目留下更多的时间,如果选择对节目的开始时间进行排序的话就会导致有一个节目迟迟不结束使得其他节目又没法开始的现象.右端点最小的第一个区间时一定要选的.这个可以简单地将其他右端值更大的区间跟他交换,就会发现不会产生更多的区间数,只会小于等于我们