我正在尝试实施硬币问题,问题规范是这样的
创建一个函数来计算可用于给定数量的所有可能的硬币组合。
All possible combinations for given amount=15, coin types=1 6 7
1) 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2) 1,1,1,1,1,1,1,1,1,6,
3) 1,1,1,1,1,1,1,1,7,
4) 1,1,1,6,6,
5) 1,1,6,7,
6) 1,7,7,
函数原型:
int findCombinationsCount(int amount, int coins[])
假设硬币阵列已排序。对于上面的例子,这个函数应该返回6.
有人指导我如何实现这个??
答案 0 :(得分:34)
使用递归。
int findCombinationsCount(int amount, int coins[]) {
return findCombinationsCount(amount, coins, 0);
}
int findCombinationsCount(int amount, int coins[], int checkFromIndex) {
if (amount == 0)
return 1;
else if (amount < 0 || coins.length == checkFromIndex)
return 0;
else {
int withFirstCoin = findCombinationsCount(amount-coins[checkFromIndex], coins, checkFromIndex);
int withoutFirstCoin = findCombinationsCount(amount, coins, checkFromIndex+1);
return withFirstCoin + withoutFirstCoin;
}
}
您应该检查此实现。我这里没有Java IDE,而且我有点生疏,所以可能会有一些错误。
答案 1 :(得分:13)
您可以使用生成函数方法来提供使用复数的快速算法。
给定硬币值c1,c2,..,ck,得到求和的方法数n,你需要的是x ^ n的系数
(1 + x^c1 + x^(2c1) + x^(3c1) + ...)(1+x^c2 + x^(2c2) + x^(3c2) + ...)....(1+x^ck + x^(2ck) + x^(3ck) + ...)
与
中找到x ^ n的系数相同1/(1-x^c1) * 1/(1-x^c2) * ... * (1-x^ck)
现在使用复数,x ^ a - 1 =(x-w1)(x-w2)...(x-wa)其中w1,w2等是统一的复杂根。
所以
1/(1-x^c1) * 1/(1-x^c2) * ... * (1-x^ck)
可以写成
1/(x-a1)(x-a2)....(x-am)
可以使用部分分数重写
A1/(x-a1) + A2/(x-a2) + ... + Am/(x-am)
可以很容易地找到x ^ n的系数:
A1/(a1)^(n+1) + A2/(a2)^(n+1) + ...+ Am/(am)^(n+1).
计算机程序应该能够轻松找到Ai和ai(可能是复数)。当然,这可能涉及浮点计算。
对于大n,这可能比枚举所有可能的组合更快。
希望有所帮助。
答案 2 :(得分:11)
虽然递归可以起作用,但通常是在一些关于算法和算法的大学课程中实施的任务。数据结构,我相信“动态编程”实现更有效。
public static int findCombinationsCount(int sum, int vals[]) {
if (sum < 0) {
return 0;
}
if (vals == null || vals.length == 0) {
return 0;
}
int dp[] = new int[sum + 1];
dp[0] = 1;
for (int i = 0; i < vals.length; ++i) {
for (int j = vals[i]; j <= sum; ++j) {
dp[j] += dp[j - vals[i]];
}
}
return dp[sum];
}
答案 3 :(得分:6)
递归非常简单:
def countChange(money: Int, coins: List[Int]): Int = {
def reduce(money: Int, coins: List[Int], accCounter: Int): Int = {
if(money == 0) accCounter + 1
else if(money < 0 || coins.isEmpty) accCounter
else reduce(money - coins.head, coins, accCounter) + reduce(money, coins.tail, accCounter)
}
if(money <= 0 || coins.isEmpty) 0
else reduce(money, coins, 0)
}
这是SCALA中的示例
答案 4 :(得分:3)
Aryabhatta’s answer 计算用固定硬币进行更改的方法数量 面额非常可爱,但实施起来也不切实际 描述。我们将使用模块化而不是使用复数 算术,类似于数论变换如何取代a 用于乘以整数多项式的傅里叶变换。
让D
成为硬币面额中最不常见的倍数。通过
Dirichlet关于算术进展的定理存在无限
许多素数p
,D
除以p - 1
。 (运气好的话,
它们甚至会以我们可以找到它们的方式分发
有效率。)我们将计算以p
为模的方式的数量
满足这个条件。通过某种方式获得原油界限(例如,
n + k - 1
选择k - 1
,其中n
为总数,k
为数字
(几种),用几种不同的方法重复这个过程
产品超过该范围的素数,并应用中文
余数定理,我们可以恢复确切的数字。
在我们找到素数之前,为整数1 + k*D
测试候选人k > 0
p
。设g
为原始根模p
(生成候选者)
随机并应用标准测试)。对于每个面额d
,请表达
多项式x**d - 1
modulo p
作为因子的乘积:
x**d - 1 = product from i=0 to d-1 of (x - g**((p-1)*i/d)) [modulo p].
请注意d
除D
除p-1
,所以指数确实是一个
整数。
让m
为面额的总和。收集所有常量
g**((p-1)*i/d)
为a(0), ..., a(m-1)
。下一步是找到一个
部分分数分解A(0), ..., A(m-1)
使
sign / product from j=0 to m-1 of (a(j) - x) =
sum from j=0 to m-1 of A(j)/(a(j) - x) [modulo p],
如果有多个面额且sign
为1
-1
如果有一定数量的面额。派生一个系统
A(j)
的线性方程通过评估给定的两边
对于x
的不同值的等式,然后用高斯求解它
淘汰。如果有重复,生活会变得复杂;它可能最容易选择另一个素数。
鉴于此设置,我们可以计算方式的数量(模p
当然)将变更金额n
作为
sum from j=0 to m-1 of A(j) * (1/a(j))**(n+1).
答案 5 :(得分:2)
package algorithms;
import java.util.Random;
/**`enter code here`
* Owner : Ghodrat Naderi
* E-Mail: Naderi.ghodrat@gmail.com
* Date : 10/12/12
* Time : 4:50 PM
* IDE : IntelliJ IDEA 11
*/
public class CoinProblem
{
public static void main(String[] args)
{
int[] coins = {1, 3, 5, 10, 20, 50, 100, 200, 500};
int amount = new Random().nextInt(10000);
int coinsCount = 0;
System.out.println("amount = " + amount);
int[] numberOfCoins = findNumberOfCoins(coins, amount);
for (int i = 0; i < numberOfCoins.length; i++)
{
if (numberOfCoins[i] > 0)
{
System.out.println("coins= " + coins[i] + " Count=" + numberOfCoins[i] + "\n");
coinsCount += numberOfCoins[i];
}
}
System.out.println("numberOfCoins = " + coinsCount);
}
private static int[] findNumberOfCoins(int[] coins, int amount)
{
int c = coins.length;
int[] numberOfCoins = new int[coins.length];
while (amount > 0)
{
c--;
if (amount >= coins[c])
{
int quotient = amount / coins[c];
amount = amount - coins[c] * quotient;
numberOfCoins[c] = quotient;
}
}
return numberOfCoins;
}
}
答案 6 :(得分:1)
递归解决方案可能是正确答案:
int findCombinationsCount(int amount, int coins[])
{
// I am assuming amount >= 0, coins.length > 0 and all elements of coins > 0.
if (coins.length == 1)
{
return amount % coins[0] == 0 ? 1 : 0;
}
else
{
int total = 0;
int[] subCoins = arrayOfCoinsExceptTheFirstOne(coins);
for (int i = 0 ; i * coins[0] <= amount ; ++i)
{
total += findCombinationsCount(amount - i * coins[0], subCoins);
}
return total;
}
}
警告:我没有测试过甚至编译过上述内容。
答案 7 :(得分:1)
提到的递归解决方案可行,但如果您添加更多硬币面额和/或显着提高目标值,它们将会非常缓慢。
加速它需要的是实现动态编程解决方案。看看knapsack problem。你可以调整那里提到的DP解决方案来解决你的问题,方法是保持计算总数的方式,而不是所需的最小硬币数。
答案 8 :(得分:1)
@Jordi提供的解决方案很不错但运行速度非常慢。您可以尝试输入600到该解决方案,看看它有多慢。
我的想法是使用自下而上的动态编程。
请注意,通常情况下,money = m和硬币{a,b,c}的可能组合等于
的组合如果没有可用的硬币或可用的硬币无法支付所需的金额,则应相应地填写0。如果金额为0,则应填写1。
public static void main(String[] args){
int[] coins = new int[]{1,2,3,4,5};
int money = 600;
int[][] recorder = new int[money+1][coins.length];
for(int k=0;k<coins.length;k++){
recorder[0][k] = 1;
}
for(int i=1;i<=money;i++){
//System.out.println("working on money="+i);
int with = 0;
int without = 0;
for(int coin_index=0;coin_index<coins.length;coin_index++){
//System.out.println("working on coin until "+coins[coin_index]);
if(i-coins[coin_index]<0){
with = 0;
}else{
with = recorder[i-coins[coin_index]][coin_index];
}
//System.out.println("with="+with);
if(coin_index-1<0){
without = 0;
}else{
without = recorder[i][coin_index-1];
}
//System.out.println("without="+without);
//System.out.println("result="+(without+with));
recorder[i][coin_index] = with+without;
}
}
System.out.print(recorder[money][coins.length-1]);
}
答案 9 :(得分:1)
此代码基于JeremyP提供的解决方案,该解决方案完美无缺,我只是通过使用动态编程来增强它以优化性能。我无法对JeremyP帖子发表评论,因为我没有足够的声誉:)
public static long makeChange(int[] coins, int money) {
Long[][] resultMap = new Long[coins.length][money+1];
return getChange(coins,money,0,resultMap);
}
public static long getChange(int[] coins, int money, int index,Long[][] resultMap) {
if (index == coins.length -1) // if we are at the end
return money%coins[index]==0? 1:0;
else{
//System.out.printf("Checking index %d and money %d ",index,money);
Long storedResult =resultMap[index][money];
if(storedResult != null)
return storedResult;
long total=0;
for(int coff=0; coff * coins[index] <=money; coff ++){
total += getChange(coins, money - coff*coins[index],index +1,resultMap);
}
resultMap[index][money] = total;
return total;
}
}
答案 10 :(得分:0)
第一个想法:
int combinations = 0;
for (int i = 0; i * 7 <=15; i++) {
for (int j = 0; j * 6 + i * 7 <= 15; j++) {
combinations++;
}
}
(在这种情况下,'&lt; ='是多余的,但如果您决定更改参数,则需要更通用的解决方案)
答案 11 :(得分:0)
再次使用递归测试解决方案,尽管可能不是最优雅的代码。 (注意它返回使用的每枚硬币的数量,而不是重复实际的硬币数量n次)。
public class CoinPerm {
@Test
public void QuickTest() throws Exception
{
int ammount = 15;
int coins[] = {1,6,7};
ArrayList<solution> solutionList = SolvePerms(ammount, coins);
for (solution sol : solutionList)
{
System.out.println(sol);
}
assertTrue("Wrong number of solutions " + solutionList.size(),solutionList.size() == 6);
}
public ArrayList<solution> SolvePerms(int ammount, int coins[]) throws Exception
{
ArrayList<solution> solutionList = new ArrayList<solution>();
ArrayList<Integer> emptyList = new ArrayList<Integer>();
solution CurrentSolution = new solution(emptyList);
GetPerms(ammount, coins, CurrentSolution, solutionList);
return solutionList;
}
private void GetPerms(int ammount, int coins[], solution CurrentSolution, ArrayList<solution> mSolutions) throws Exception
{
int currentCoin = coins[0];
if (currentCoin <= 0)
{
throw new Exception("Cant cope with negative or zero ammounts");
}
if (coins.length == 1)
{
if (ammount % currentCoin == 0)
{
CurrentSolution.add(ammount/currentCoin);
mSolutions.add(CurrentSolution);
}
return;
}
// work out list with one less coin.
int coinsDepth = coins.length;
int reducedCoins[] = new int[(coinsDepth -1 )];
for (int j = 0; j < coinsDepth - 1;j++)
{
reducedCoins[j] = coins[j+1];
}
// integer rounding okay;
int numberOfPerms = ammount / currentCoin;
for (int j = 0; j <= numberOfPerms; j++)
{
solution newSolution = CurrentSolution.clone();
newSolution.add(j);
GetPerms(ammount - j * currentCoin,reducedCoins, newSolution, mSolutions );
}
}
private class solution
{
ArrayList<Integer> mNumberOfCoins;
solution(ArrayList<Integer> anumberOfCoins)
{
mNumberOfCoins = anumberOfCoins;
}
@Override
public String toString() {
if (mNumberOfCoins != null && mNumberOfCoins.size() > 0)
{
String retval = mNumberOfCoins.get(0).toString();
for (int i = 1; i< mNumberOfCoins.size();i++)
{
retval += ","+mNumberOfCoins.get(i).toString();
}
return retval;
}
else
{
return "";
}
}
@Override
protected solution clone()
{
return new solution((ArrayList<Integer>) mNumberOfCoins.clone());
}
public void add(int i) {
mNumberOfCoins.add(i);
}
}
}
答案 12 :(得分:0)
下面是memoization java解决方案的递归。对于低于1,我们有1,2,3,5作为硬币,200作为目标金额。
countCombinations(200,new int[]{5,2,3,1} , 0, 0,new Integer[6][200+5]);
static int countCombinations(Integer targetAmount, int[] V,int currentAmount, int coin, Integer[][] memory){
//Comment below if block if you want to see the perf difference
if(memory[coin][currentAmount] != null){
return memory[coin][currentAmount];
}
if(currentAmount > targetAmount){
memory[coin][currentAmount] = 0;
return 0;
}
if(currentAmount == targetAmount){
return 1;
}
int count = 0;
for(int selectedCoin : V){
if(selectedCoin >= coin){
count += countCombinations(targetAmount, V, currentAmount+selectedCoin, selectedCoin,memory);
}
}
memory[coin][currentAmount] = count;
return count;
}
答案 13 :(得分:0)
#include<iostream>
using namespace std;
int solns = 0;
void countComb(int* arr, int low, int high, int Val)
{
bool b = false;
for (size_t i = low; i <= high; i++)
{
if (Val - arr[i] == 0)
{
solns++;
break;
}
else if (Val - arr[i] > 0)
countComb(arr, i, high, Val - arr[i]);
}
}
int main()
{
int coins[] = { 1,2,5 };
int value = 7;
int arrSize = sizeof(coins) / sizeof(int);
countComb(coins,0, arrSize,value);
cout << solns << endl;
return 0;
}
答案 14 :(得分:-1)
public static void main(String[] args) {
int b,c,total = 15;
int combos =1;
for(int d=0;d<total/7;d++)
{
b = total - d * 7;
for (int n = 0; n <= b /6; n++)
{
combos++;
}
}
System.out.print("TOTAL COMBINATIONS = "+combos);
}
答案 15 :(得分:-8)
硬币(1,5,10,25,50)的相同问题具有以下解决方案之一。 解决方案应满足以下等式: 1 * a + 5 * b + 10 * c + 25 * d + 50 * e ==美分
public static void countWaysToProduceGivenAmountOfMoney(int cents){
for(int a = 0;a<=cents;a++){
for(int b = 0;b<=cents/5;b++){
for(int c = 0;c<=cents/10;c++){
for(int d = 0;d<=cents/25;d++){
for(int e = 0;e<=cents/50;e++){
if(1*a + 5*b + 10*c + 25*d + 50*e == cents){
System.out.println("1 cents :"+a+", 5 cents:"+b+", 10 cents:"+c);
}
}
}
}
}
}
}
可针对任何常规解决方案进行修改。