每周一道算法题:兑换零钱

问题:

已知可兑换的零钱种类有1元,5元,10元,20元4种,现在有100块钱要换成零钱且总数量少于15张,有几种换法?分别是什么?

思路:

已知有[1,5,10,20]这样的一个可选数据集S,现在要从中取出n个数,每个数的张数为a,使得a1xn1+a2xn2+...aixni = 100。

最大的面额是20,总共需要100/20=5张,这是最少的张数,所以循环的下限是5,上限题目已经限定了,是15。

也就是说,从S这个数据集中,取5-15个数,使得他们的和为100,数是可以重复的。

先考虑最简单的情况。先不考虑和为100这个限制。仅仅是从数据集S中取5个数,数字可以重复,这种情况有多少种?

在我们这个问题中,对取出来的数的顺序是没有要求的,比如:取出来是[1,20,10,5,5]与[1,5,5,10,20]没有区别,所以这是个组合的问题。
但一般的组合问题都是从m个不同的数里选n个,公式就是C(m,n)。
但我们这里的数是允许重复的,怎么办?可以转换一下,假如我们取了[1,1,1,1,1]这5个数(即n=5),我们可以保持第一个1不变,余下的4个数(即n-1)全部加上一个100以上的随机数,使得其与S中别的元素都不同,那么便相当于从m+n-1个不同的数中取出n个数,公式是C(m+n-1, n)。验证一下。

假如S为[1,5],从中取2个,可以得到如下的组合:
[1,1],[1,5],[5,5],共计3种。
用公式算,C(2+2-1,2) = C(3,2)=3;正确。
再假如从中取3个,可以得到如下组合:
[1,1,1],[5,1,1],[5,5,1],[5,5,5],共计4种。
用公式算,C(2+3-1,3)=4;正确。

所以从m个不同数中取出n个可重复的数的组合数量是C(m+n-1,n)

现在要把这些组合列出来,可以用如下的办法:
我们建立一个临时数组,用来存放S中各元素的下标。
初始化为[0,0,0,0,0],表示取的5个元素全是S[0];
接着对首位进行加1,变为[1,0,0,0,0],表示的金额为[5,1,1,1,1];
首位继续累加,直到编历完所有的S,最后会变为[3,0,0,0];

这是第一轮的过程,即:
[0,0,0,0,0]
[1,0,0,0,0]
[2,0,0,0,0]
[3,0,0,0,0]

然后进行下一轮,当首位元素的下标超过3时,对第2位进行处理,即处理成:
[1,1,0,0,0]
接着继续累加首位
[2,1,0,0,0]
[3,1,0,0,0]

当首位超过3时,对第2位进行累加
[1,2,0,0,0]
接首继续累加首位
[2,2,0,0,0]
[3,2,0,0,0]

重复这个过程,直到所有的元素变为
[3,3,3,3,3]

但全部为3这个条件不好判断,我们可以增加一位,让下标数组多出1位来,这样,当第6个元素有值时,即认为已经结束了。
即当元素下标成为[1,1,1,1,1,1]时,程序退出。

把所有的下标数组汇总到一起,并映射到数据集的对应元素上,即为最终结果。

解答:

php

/**
 * 取元素可重复的组合
 * @param $arr array 可选择元素数组
 * @param $n int 要取的数量
 * @return array
 */
function repeatedCombination($arr, $n)
{
    $len = count($arr);// 数组长度
    $tmp = array_fill(0, $n + 1, 0);// 存放组合元素的下标,初始为0
    $len--; // 数组长度减1
    $result = array();
    while (1) {
        for ($i = 0; $i < $n; ++$i) { // n个元素
            if ($tmp[$i] > $len) {
                $tmp[$i + 1] += 1;
                for ($j = $i; $j >= 0; --$j) {
                    $tmp[$j] = $tmp[$j + 1];
                }
            }
        }

        // 当最后一位非0时,所有的组合都已经处理完毕,退出
        if ($tmp[$n] > 0) {
            break;
        }

        $item = array();
        for ($i = 0; $i < $n; $i++) {
            $item[] = $arr[$tmp[$i]];
        }
        $result[] = $item;
        $tmp[0] += 1;
    }
    return $result;
}

$arr = array("1", "5", "10", "20");

$m = array();
for ($i = 5; $i < 15; $i++) {
    $rs = repeatedCombination($arr, $i);
    foreach ($rs as $r) {
        if (array_sum($r) == 100) {
            $m[] = $r;
        }
    }
}
print_r($m);

输出:

Array
(
    [0] => 20-20-20-20-20
    [1] => 20-20-20-20-10-10
    [2] => 20-20-20-20-10-5-5
    [3] => 20-20-20-10-10-10-10
    [4] => 20-20-20-20-5-5-5-5
    [5] => 20-20-20-10-10-10-5-5
    [6] => 20-20-10-10-10-10-10-10
    [7] => 20-20-20-10-10-5-5-5-5
    [8] => 20-20-10-10-10-10-10-5-5
    [9] => 20-10-10-10-10-10-10-10-10
    [10] => 20-20-20-10-5-5-5-5-5-5
    [11] => 20-20-10-10-10-10-5-5-5-5
    [12] => 20-10-10-10-10-10-10-10-5-5
    [13] => 10-10-10-10-10-10-10-10-10-10
    [14] => 20-20-20-20-10-5-1-1-1-1-1
    [15] => 20-20-20-5-5-5-5-5-5-5-5
    [16] => 20-20-10-10-10-5-5-5-5-5-5
    [17] => 20-10-10-10-10-10-10-5-5-5-5
    [18] => 10-10-10-10-10-10-10-10-10-5-5
    [19] => 20-20-20-20-5-5-5-1-1-1-1-1
    [20] => 20-20-20-10-10-10-5-1-1-1-1-1
    [21] => 20-20-10-10-5-5-5-5-5-5-5-5
    [22] => 20-10-10-10-10-10-5-5-5-5-5-5
    [23] => 10-10-10-10-10-10-10-10-5-5-5-5
    [24] => 20-20-20-10-10-5-5-5-1-1-1-1-1
    [25] => 20-20-10-10-10-10-10-5-1-1-1-1-1
    [26] => 20-20-10-5-5-5-5-5-5-5-5-5-5
    [27] => 20-10-10-10-10-5-5-5-5-5-5-5-5
    [28] => 10-10-10-10-10-10-10-5-5-5-5-5-5
    [29] => 20-20-20-10-5-5-5-5-5-1-1-1-1-1
    [30] => 20-20-10-10-10-10-5-5-5-1-1-1-1-1
    [31] => 20-10-10-10-10-10-10-10-5-1-1-1-1-1
    [32] => 20-20-5-5-5-5-5-5-5-5-5-5-5-5
    [33] => 20-10-10-10-5-5-5-5-5-5-5-5-5-5
    [34] => 10-10-10-10-10-10-5-5-5-5-5-5-5-5
)

golang:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    data := []string{"1", "5", "10", "20"}
    m := make([][]string, 0)
    for i := 5; i < 15; i++ {
        rs := repeatedCombination(data, i)
        for _, r := range rs {
            sum := 0
            for _, v := range r {
                val, _ := strconv.Atoi(v)
                sum += val
            }
            if sum == 100 {
                m = append(m, r)
            }
        }
    }
    fmt.Println(m)
}

func repeatedCombination(data []string, n int) [][]string {
    length := len(data)
    limit := length - 1
    tmp := make([]int, n+1)
    result := make([][]string, 0)
    for {
        for i := 0; i < n; i++ {
            if tmp[i] > limit {
                tmp[i+1] += 1
                for j := i; j >= 0; j-- {
                    tmp[j] = tmp[j+1]
                }
            }
        }

        if tmp[n] > 0 {
            break
        }

        var item []string
        for i := 0; i < n; i++ {
            item = append(item, data[tmp[i]])
        }
        result = append(result, item)

        tmp[0] += 1
    }
    return result
}

输出:

[[20 20 20 20 20]
[20 20 20 20 10 10]
[20 20 20 20 10 5 5]
[20 20 20 10 10 10 10]
[20 20 20 20 5 5 5 5]
[20 20 20 10 10 10 5 5]
[20 20 10 10 10 10 10 10]
[20 20 20 10 10 5 5 5 5]
[20 20 10 10 10 10 10 5 5]
[20 10 10 10 10 10 10 10 10]
[20 20 20 10 5 5 5 5 5 5]
[20 20 10 10 10 10 5 5 5 5]
[20 10 10 10 10 10 10 10 5 5]
[10 10 10 10 10 10 10 10 10 10]
[20 20 20 20 10 5 1 1 1 1 1]
[20 20 20 5 5 5 5 5 5 5 5]
[20 20 10 10 10 5 5 5 5 5 5]
[20 10 10 10 10 10 10 5 5 5 5]
[10 10 10 10 10 10 10 10 10 5 5]
[20 20 20 20 5 5 5 1 1 1 1 1]
[20 20 20 10 10 10 5 1 1 1 1 1]
[20 20 10 10 5 5 5 5 5 5 5 5]
[20 10 10 10 10 10 5 5 5 5 5 5]
[10 10 10 10 10 10 10 10 5 5 5 5]
[20 20 20 10 10 5 5 5 1 1 1 1 1]
[20 20 10 10 10 10 10 5 1 1 1 1 1]
[20 20 10 5 5 5 5 5 5 5 5 5 5]
[20 10 10 10 10 5 5 5 5 5 5 5 5]
[10 10 10 10 10 10 10 5 5 5 5 5 5]
[20 20 20 10 5 5 5 5 5 1 1 1 1 1]
[20 20 10 10 10 10 5 5 5 1 1 1 1 1]
[20 10 10 10 10 10 10 10 5 1 1 1 1 1]
[20 20 5 5 5 5 5 5 5 5 5 5 5 5]
[20 10 10 10 5 5 5 5 5 5 5 5 5 5]
[10 10 10 10 10 10 5 5 5 5 5 5 5 5]]

原文地址:https://blog.51cto.com/ustb80/2427833

时间: 2024-10-09 22:40:18

每周一道算法题:兑换零钱的相关文章

每周一道算法题005:切木棒

问题: 假设要把长度为n厘米的木棒切分为1厘米长的小段,但是1根木棒只能由1人切分,当木棒被切分为3段后,可以同时由3个人分别切分木棒.求最多有m个人时,最少要切分几次. 譬如n=8,m=3时如下图所示,切分4次就可以了. 求当n=20,m=3时的最少切分次数.求当n=100,m=5时的最少切分次数. 思路: 这道题最难的不是算法,而是理解题意.木棒刚开始只有1根,题目规定"1根木棒只能由1人切分",此时由1人切分后,变成2根:2根木棒再做切分,也要满足"1根木棒只能由1人切

每周一道算法题003:翻牌

问题: 有100张写着数字1-100的牌,并按顺序排列.最开始所有牌都是背面朝上放置.某人从第2张牌开始,隔1张牌翻牌.然后第2, 4, 6, -,100张牌就会变成正面朝上.接下来,另一个人从第3张牌开始,隔2张牌翻牌(原本背面朝上的,翻转成正面朝上:原本正面朝上的,翻转成背面朝上).再接下来,又有一个人从第4张牌开始,隔3张牌翻牌.像这样,从第n张牌开始,每隔n-1张牌翻牌,直到没有可翻动的牌为止.求当所有牌不再变动时,所有背面朝上的牌的数字. 思路: 这道题思路有很多种: 思路1设i为轮次

每周一道算法题001:回文数

题目: 找出大于10的最小的2进制,8进制,10进制都是回文数的最小的数.回文数指的是正读和反读都是一样的数,例如:33,10001,123454321... 思路: 先转换进制,然后统一处理成字符串进行比较 解答: PHP function execute(){ $x = 11; while (1) { if ($x == strrev($x) && decbin($x) == strrev(decbin($x)) && decoct($x) == strrev(deco

每周一道算法题002:四则运算

问题: 求位于1000-9999,满足如下条件的数: 各位数字间加入四则运算符,也可省略,使得按四则运算计算的结果为原数字的各位数逆序排列. 例如:351 → 3×51 = 153 思路: 遍历1000-9999所有的数字,切分并组合运算符,拼成四则运算的算式然后计算.4位数中最大的数是9999,他可以被拆成999+9=1008,逆排后不可能等于原数,减法和除法不可能计算出比原数更大的数,所以只需要考虑乘法. php提供了eval函数,可以很方便的计算,但是需要对数字进行处理,因为08开头的数字

每周一道算法题006:抽签组合

问题: 有如下3支队伍,每个队伍都有2名队员.team1:A,B;team2:C,D;team3:E,F; 现在每个队出1个人,组成一个队去探险,请列出所有的组队方式. 思路: 这就是一个组合的问题,每个队里挑一人,那么总共应该有222=8种组合方式.如果暴力求解,那就是三层循环嵌套.但如果问题扩展一下,变成10个队,每个队10人,就无法暴力求解了,至少代码是没有扩展性的. 有如下一种思路: 循环所有的队伍第一次取出A,B两名队员,存起来:第二次取出C,D两名队员,与前一轮存下的队员进行交叉组合

每周一道算法题009:找二进制对称的日期

题目: 把年月日表示为YYYYMMDD这样的8位整数,然后把这个整数转换成二进制数并且逆序排列,再把得到的二进制数转换成十进制数,求与原日期一致的日期.求得的日期要在上一次东京奥运会(1964年10月10日)到下一次东京奥运会(预定举办日期为2020年7月24日)之间. 思路: 从起始时间开始逐天累加,对每一天进行进制转换并反转,然后比较,如果相同就输出,不同就继续,直至到达结束时间. 解答: php: function findDate($begin, $end) { $beginDate =

每周一道算法题010:扫地机器人路径统计

问题: 假设有一款不会反复清扫同一个地方的机器人,它只能前后左右移动.举个例子,如果第1次向后移动,那么连续移动3次时,就会有以下9种情况(图6).又因为第1次移动可以是前后左右4种情况,所以移动3次时全部路径有9×4=36种. 求这个机器人移动12次时,有多少种移动路径? 思路: 尝试用递归和非递归两种办法来解. 递归思路:从起点开始,在各方向移动1步,如果移动后的点不在当前的路径中,就加入到当前路径中,并进行下一次移动,当移到到指定的N步时,退出,并计数加1,视为找到一条路径. 非递归思路:

每周一道算法题011:最长公共子串

问题: 求以下几组单词的最长公共子串的长度1.fish和fosh2.fish和hish3.fish和vista 思路: 可以用表格法,横纵坐标分别是两个单词,如果字符相同,就用左上角的数字加1,最后取表格中的最大值. 解答: php: <?php // 找出两个单词的最长公共子串 function findLongestSubString($word1, $word2) { $len1 = strlen($word1); $len2 = strlen($word2); $cell = array

一天一道算法题---6.26---二分查找

感谢微信平台---一天一道算法题----每天多一点进步-- 好累啊  现在在用win7自带的输入法 打起来真麻烦 快点把这2天的搞完就重装了 还是直接来源于----〉 待字闺中 分析 给定一个数组A,其中有一个位置被称为Magic Index,含义是:如果i是Magic Index,则A[i] = i.假设A中的元素递增有序.且不重复,请给出方法,找到这个Magic Index.更进一步,当A中允许有重复的元素,该怎么办呢? 没有重复元素的情况 一些同学在遇到这个题目的时候,往往会觉得比较简单.