【问题标题】:Recursive method to count the number of combinations计算组合数的递归方法
【发布时间】:2014-01-13 09:38:36
【问题描述】:

这是一个 java 代码,用于递归计算特定硬币(例如 1、2、5、20、50 等)的支付次数。我试图弄清楚它是如何工作的,但似乎无法理解。有人可以这么好心地解释一下代码背后的数学和逻辑以及这种递归是如何工作的吗?我真的很感激。

    // Returns the count of ways we can sum  S[0...m-1] coins to get sum n
int count( int S[], int m, int n ){

    // If n is 0 then there is 1 solution (do not include any coin)
    if (n == 0)
        return 1;

    // If n is less than 0 then no solution exists
    if (n < 0)
        return 0;

    // If there are no coins and n is greater than 0, then no solution exist
    if (m <=0 && n >= 1)
        return 0;

    // count is sum of solutions (i) including S[m-1] (ii) excluding S[m-1]
    return count( S, m - 1, n ) + count( S, m, n-S[m-1] );
}

【问题讨论】:

  • 怎么称呼?什么是 S?
  • @Behe 看起来像要求和的硬币数组
  • @Java Devil 没错,它看起来像一个整数数组

标签: java recursion


【解决方案1】:

该方法的工作原理如下:

  1. 第一个语句是当前递归的停止条件(在所有情况下如果没有这些,那么您最终会出现一个无限循环,最终以 StackOverFlow 结束)

  2. 最后一行是计算发生的地方。每个语句都通过以下方式将问题简化为更小的块:

    • count( S, m - 1, n ) 减少硬币的数量 (m-1),不包括在下一次递归调用中的最后一个硬币
    • count( S, m, n-S[m-1]) 使用数组中的最后一个硬币,并通过该硬币的价值减少需要达到的总和

考虑这个小例子:

S[] = {1,2)    // We have a 1 and 2 cent coin
m   = S.length // Consider all possibilities  ( = 2)
n   = 3        // How many ways can we make 3c
               // Obviously with: 1x1c + 1x2c
               //            and: 3x1c

作为树的递归;左分支=count( S, m - 1, n ),右分支=count( S, m, n-S[m-1])

                                  m=2;n=3
                                 /        \
                          m=1;n=3          m=2;n=1
                         /       \       /     \
                  m=0;n=3    m=1;n=2   m=1;n=1  m=2;n=-1
                            /     \     /     \
                     m=0;n=2  m=1;n=1 m=0;n=1  m=1;n=0
                             /     \
                       m=0;n=1   m=1;n=0

这个递归可以被认为是对这棵树的Pre-order遍历。

如果您然后考虑找到解决方案的方法的条件。所以在 n = 0 的叶子节点处。

每一个都是这样产生的:

第一个解决方案

  1. m=1;n=3 - 排除最后一个硬币 (2c)
  2. m=1;n=2 - 使用这个硬币 (1c) 并减少 1
  3. m=1;n=1 - 使用这个硬币 (1c) 并减少 1
  4. m=1;n=0 - 使用这个硬币 (1c) 并减少 1
  5. n = 0 - 一个解 (3x1c)

第二个解决方案

  1. m=2;n=1 - 使用这个硬币(2c)并减少 2
  2. m=1;n=1 - 排除最后一个硬币 (2c)
  3. m=1;n=0 - 使用这个硬币 (1c) 并减少 1
  4. n = 0 - 一个解 (1x2c + 1x2c)

在每个节点处返回一个值 - 0(无解决方案)或 1(解决方案) - 以添加到找到的解决方案的总数中。一旦递归结束,这个最终值就会被返回,它是解决方案的数量。


一些补充说明:

  • 这段代码将只考虑数组S 中的第一个m 硬币,因此要考虑对方法的初始调用需要m == S.length 的所有可能方式
  • 假设每个硬币可以多次使用

使用打印语句修改代码以查看递归:

public static void main(String[] args){
    int[] coins = new int[]{1,2};
    System.out.println("Final Count = " + count(coins, coins.length, 3, ""));
}

public static int calls = 0;

public static int count( int S[], int m, int n , String from){
    calls++;
    System.out.print("Call#" + calls + ": " + from + "; m = " + m + "; n = " + n);

    // If n is 0 then there is 1 solution (do not include any coin)
    if (n == 0)
    {
        System.out.println(" - Solution Found");
        return 1;
    }

    // If n is less than 0 then no solution exists
    if (n < 0)
    {
        System.out.println(" - No Solution Found n < 0");
        return 0;
    }

    // If there are no coins and n is greater than 0, then no solution exist
    if (m <=0 && n >= 1)
    {
        System.out.println(" - No Solution Found (other Case)");
        return 0;
    }

    System.out.println();
    // count is sum of solutions (i) including S[m-1] (ii) excluding S[m-1]
    return count( S, m - 1, n , from + "E" ) + count( S, m, n-S[m-1], from + "I" );
}

【讨论】:

  • 哇,非常非常非常感谢您的详尽解释。这真的是我需要的。抱歉回复晚了,我花了一些时间才得到它,但现在很清楚:)
  • 很抱歉再次打扰您...但我还有一个问题。我只想在控制台上打印有效组合(对于上面的示例,如果数组仅包含 {1, 2}n=3,则类似于 1+1+11+2)但是当我添加第一个 @987654337 @语句代码System.out.println("(n)"+n+" + "+m+"(m)");eclipse将下一个if语句(即if (n &lt; 0))标记为编译错误unreachable code...你能告诉我为什么这不起作用吗?...并且还尝试了其他不同的方法但是通过发布它们会使消息太长而无法传达:(谢谢
  • @user3185735 我只能假设在您的if(...) 之后,您缺少开头的{ 和闭合的} 大括号。确保它的格式为if(condition){statements}。如果您还没有这样做,那么只有if 之后的第一条语句是有条件的,并且后续行将始终被执行......作为返回语句,因此其余代码将无法访问。如果不是这种情况,请写一个新问题并给我一个链接。
  • 你是对的,brackets 不见了...我知道我是菜鸟,但不是这个菜鸟...遗憾的是仍然没有按我希望的方式打印组合所以回到方块 1...我也试过这个:System.out.println(count( S, m - 1, n )+" + "+ count( S, m, n-S[m-1] )+" = "+count( S, m - 1, n ) + count( S, m, n-S[m-1] )); 在递归 return 调用之前,但它打印的只是 0 + 1 = 01 并导致 StackOverFlow ......这是我没有得到的东西。 .. 我虽然应该打印包括S[m-1] 和不包括S[m-1] 在内的组合总和...递归是thougt...
  • @user3185735 StackOverFlow 很可能意味着您遇到了无限循环。那是太多的递归。递归打印字符串可能很困难。我认为你最好用你想要的和迄今为止尝试过的内容写一个新问题。
【解决方案2】:

从代码中,我假设S 是一个至少包含m 元素的数组,每个元素代表一个可用的硬币面额,n 是预期的总和。

cmets 真的说了很多,只是最后的评论是倒退的。 count( S, m - 1, n ) 是当前范围内不包括最后一个硬币的解决方案数。 count( S, m, n-S[m-1] ) 是使用该硬币的解决方案的数量。

排除情况只是通过将m 减一来丢弃当前范围内的最后一个硬币。

包含案例通过将n 减去该硬币的价值来使用它。由于包含情况也不会减少m,因此推测任何硬币面额都可以多次使用。硬币是否太大无关紧要 - 如果n &lt; 0,则通过返回 0 来处理。

如果 cmets 对基本案例有任何不清楚的地方,请提出具体问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    相关资源
    最近更新 更多