每周一道算法题:兑换零钱
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每周一道算法题:兑换零钱相关的知识,希望对你有一定的参考价值。
问题:已知可兑换的零钱种类有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]时,程序退出。
把所有的下标数组汇总到一起,并映射到数据集的对应元素上,即为最终结果。
解答:
/**
* 取元素可重复的组合
* @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]]
以上是关于每周一道算法题:兑换零钱的主要内容,如果未能解决你的问题,请参考以下文章