深入解析之将100元兑换为1元、5元、10元的零钱,请问有多少种兑换方法

            要将100元兑换为1元、5元、10元的零钱,请问有多少种兑换方法?这道算法题不知不觉走进了我的世界,引起了我极大的兴趣。现在就将我对它的研究分享出来,供大家点评。

       看到这个题目的第一感觉就是一个三元一次方程的求解,编程的话,就是三个for循环外加个if判断,瞬间KO。对这个题目来说效率也是可以接受的。可是这根本没有体现出算法的优势。下面我们来仔细推敲下这里面隐藏的规律。           

    

            根据上图的规律,即可得到如下代码:

     /**
       * 1、求解特定实例:要将100元兑换为1元、5元、10元的零钱,请问有多少种兑换方法?
       * 
       * @return
       * @author chenchanghan
       */
      public static int getDivideWays() {
            int count = 0;
            for (int i = 0, size = 100 / 10; i <= size; i++) {
                  // 针对10的每个场景,计算5的组合情况(即,从0个5 到 n( n=(100 - i * 10)/5
                  // )个5共n+1种情况
                  count += (100 - i * 10) / 5 + 1;
            }
            return count;
      }

到这里,这个就算解完了,但是这里确实因为分解的元素中包含1,将问题变的简单化了,如果不是1、5、10而是随意的三个数字,改怎么解决呢?同样还是要找出规律来。

下面我们就来分析下10、5、3如何组合成100吧。

首先,0个10的情况下,5和3怎么组合成100呢?正好20*5=100,显然这是不存在10的情况下出现最多5的情况,那还有没有其他的组合情况呢?这时我们就要用到一个最小公倍数(3和5的最小公倍数是15),很显然,我们就可以将”3个5替换成5个3“了。因为最多20个5,所以我们可以继续用”3个5替换成5个3“,直到最后剩下2个5。综上0个10的情况下,5可以出现的次数分别为20、17、14、11、8、5、2,所以该场景下共有7中组合方式。

其次,1个10的情况下,5和3怎么组合成100呢?我们还是从5来出发,5*18=90,1个10的情况下,组合成100,最多可以出现18次,同理还是用”3个5替换成5个3“。最终1个10的情况下,5可以出现的次数分别为18、15、12、9、6、3、0。该场景下也有7种组合方式。

同理,依次分析下去。

根据上面的规律,得出代码如下:

     /**
       * 2、组合元素一般化:将total元兑换为large元、middle元、small元的零钱,请问有多少种兑换方法?
       * 
       * @param total
       * @param large
       * @param middle
       * @param small
       * @return
       * @author chenchanghan
       */
      public static int getDivideWays(int total, int large, int middle, int small) {
            if (total > 0 && small > 0 && middle > small && large > middle) {
                  int count = 0;
                  int LCM = getLeastCommonMutiple(middle, small);
                  int substituteUnit = LCM / middle;
                  for (int i = 0, size = total / large; i <= size; i++) {
                        int restTotal = total - i * large;
                        if (restTotal > 0) {
                              // actualMaxMiddleNum>=0,表示restTotal正好可以有x个middle和y个small拼凑起来(x、y是大于等于0的整数)
                              int actualMaxMiddleNum = getActualMaxMiddleNum(restTotal, middle, small);
                              if (actualMaxMiddleNum >= substituteUnit) {
                                    // actualMaxMiddleNum >=substituteUnit,表示可以将substituteUnit个middle替换成LCM/small个small
                                    // 可以换多少次呢?显然可以换0、1...actualMaxMiddleNum/substituteUnit,即:actualMaxMiddleNum/substituteUnit+1
                                    count += actualMaxMiddleNum / substituteUnit + 1;
                              } else if (actualMaxMiddleNum >= 0) {
                                    // 0<=actualMaxMiddleNum<substituteUnit,表示正好可以勉强匹配
                                    // 因为<substituteUnit,所以无法找到一个活动的最小公倍数,来实施middle和small的互换。
                                    count++;
                              }
                        } else {
                              // 正好被large完美匹配了
                              count++;
                        }
                  }
                  return count;
            } else {
                  throw new IllegalArgumentException();
            }
      }
      
      /**
       * 获得方程:x*middle + y*small = restTotal 中x最大的取值。
       * 
       * @param restTotal
       * @param middle
       * @param small
       * @return
       * @author chenchanghan
       */
      private static int getActualMaxMiddleNum(int restTotal, int middle, int small) {
            int modMiddle = restTotal % middle;
            int maxMiddleNum = restTotal / middle;
            int actualMaxMiddleNum = -1;
            if (modMiddle == 0 || modMiddle == small) {
                  actualMaxMiddleNum = maxMiddleNum;
            } else {
                  // 无法使用最大数量(即:maxMiddleNum)的middle和small组合成restTotal,
                  // 则需要逐步减少middle的个数,进而增加small的个数,来尝试组合成restTotal。
                  int minusMiddleNum = getMinusMiddleNum(middle, small, modMiddle, maxMiddleNum);
                  if (minusMiddleNum > 0) {
                        // 表示可以形成一个拥有最大middle数的组合,即: (maxMiddleNum - minusMiddleNum)*middle + y*small = restTotal ;
                        actualMaxMiddleNum = maxMiddleNum - minusMiddleNum;
                  } else {
                        // middle和small无论怎么组合都无法拼凑成restTotal,即:x*middle + y*small = restTotal 的整数解不存在
                        actualMaxMiddleNum = -1;
                  }
            }
            return actualMaxMiddleNum;
      }

      /**
       * 
       * @param middle
       * @param small
       * @param modMiddle
       * @param maxMiddleNum
       * @return
       * @author chenchanghan
       */
      private static int getMinusMiddleNum(int middle, int small, int modMiddle, int maxMiddleNum) {
            int minusMiddleNum = -1;
            for (int i = 1; i <= maxMiddleNum; i++) {
                  if ((middle * i + modMiddle) % small == 0) {
                        minusMiddleNum = i;
                        break;
                  }
            }
            return minusMiddleNum;
      }

      /**
       * 求两个数的最小公倍数。
       * 
       * @param middle
       * @param small
       * @return
       * @author chenchanghan
       */
      private static int getLeastCommonMutiple(int m, int n) {
            return m * n / getGreatestDivisor(m, n);
      }

      /**
       * 求两个数的最大公约数。
       * 
       * @param m
       * @param n
       * @return
       * @author chenchanghan
       */
      private static int getGreatestDivisor(int m, int n) {
            int tmp = 0;
            if (m < n) {
                  tmp = m;
                  m = n;
                  n = tmp;
            }
            while ((tmp = m % n) != 0) {
                  m = n;
                  n = tmp;
            }
            return n;
      }


我们再来推广下,将分解的元素变成3个以上,具体见如下代码:

     /**
       * 3、元素个数一般化:将total元兑换为a元、b元、c元、....的零钱,请问有多少种兑换方法?
       * 
       * @param total
       * @param elements
       * @return
       * @author chenchanghan
       */
      public static int getDivideWays(int total,int[] elements){
            if(elements!=null && elements.length>=3){
                  int count = 0 ;
                  if(elements.length == 3){
                        count += getDivideWays(total,elements[0],elements[1],elements[2]);
                  }else{
                        int large = elements[0];
                        int[] subElements = new int[elements.length-1];
                        System.arraycopy(elements, 1, subElements, 0, subElements.length);
                        for (int i = 0, size = total / large; i <= size; i++) {
                              int restTotal = total - i * large;
                              if (restTotal != 0) {
                                    count += getDivideWays(restTotal, subElements);
                              } else {
                                    count++;
                              }
                        }
                  }
                  return count ;
            }else{
                  throw new IllegalArgumentException();
            }
      }


        


        



  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值