如今有若干不一样面额的零钱,供顾客来换。零钱种类有 0.5美圆,0.25美圆,10美分,5美分和1美分五种(这里也能够自定义,程序改动的地方也很简单)。
计算当顾客用a元换零钱时,共有多少种兑换方法?算法
将总数为a的现金换成n种硬币的不一样方式的数目等于:
- 将现金数a换成除第一种硬币以外的全部其它硬币的不一样方式的数目,加上
- 将现金数a-d换成全部种类的硬币的不一样方式数目,其中d是第一种硬币的面额app
咱们根据上面的算法定义,就能够获得以下算法:
- 若是a=0,属于兑换成功,所以属于1中兑换方式
- 若是a<0,兑换失败,属于0种兑换方式
- 若是n=0,兑换失败,属于0中兑换方式jvm
(define (count-change amount) (cc amount 5)) (define (cc amount kinds-of-coins) (cond ((= amount 0) 1) ((or (< amount 0) (= kinds-of-coins 0)) 0) (else (+ (cc amount (- kinds-of-coins 1)) (cc (- amount (first-denomination kinds-of-coins)) kinds-of-coins))))) (define (first-denomination kinds-of-coins) (cond ((= kinds-of-coins 1) 1) ((= kinds-of-coins 2) 5) ((= kinds-of-coins 3) 10) ((= kinds-of-coins 4) 25) ((= kinds-of-coins 5) 50))) (count-change 100) ;;292
咱们知道,递归有一个缺点,若是不能作到尾递归消除,那么,调用栈很快会爆炸,所以,上面的解法只能计算比较小的值,若是有面额10000的,
可能就没办法了。
递归和循环是能够替换的,只是有些递归方法转换成循环很是麻烦,甚至仅仅是理论上的可转换(如ackerman函数,可能都作不到循环,这我不知道啊!我只是打个比方)
可是循环有一个巨大的优点,它不会消耗栈空间,所以,若是能将上面的方法改写为循环的方式(或者是尾递归),那么就能够计算很大的值了。函数
由于jvm不支持尾递归,所以,clojure提供了recur
函数,能够将尾递归转换为循环形式。下面就是clojure的解法oop
;; money change ;; $1/2 $1/4 $1/10 $1/20 $1/100 ;;半美圆,1/4美圆,10美分,5美分,1美分 换零钱 ;;多少种换法 (def money-kinds [50 25 10 5 1]) (defn finish? ;;判断该参数列表是否已经计算完毕 ;;完成条件: ;;1,可兑换的硬币种类只剩下一种(这里经过判断元素个数是否为2),即1美分的(注意, ;;若是最小的硬币面额不是1美分的,还要判断 ;;当前的余额是否可以整除,若是不能整除,则属于不能兑换的状况),返回0 ;;2,若是当前余额为0,说明已经兑换完了,返回0 ;;3,若是当前余额为负数,说明兑换失败了,返回0,表示本次兑换无效,不能计入总数 ;;4,若是不知足以上状况,说明尚未兑换结束,直接返回该参数列表 ;;例:[25,10,5,1,50] -> [25,10,5,1,50] ;; [1,25] -> 1 ;; [10,5,1,0] -> 1 ;; [10,5,1,-5] -> 0 [coins&money] (cond (= 2 (count coins&money)) 1 (= 0 (last coins&money)) 1 (> 0 (last coins&money)) 0 :default coins&money)) (defn change-helper ;;处理当前的参数列表,也就是换零钱的递归定义 ;;例: [100,50,25,10,5,1,100] -> [[50,25,10,5,1,100] [100,50,25,10,5,1,100-100]] [coins&money] [(subvec coins&money 1 (count coins&money)) (conj (pop coins&money) (- (peek coins&money) (first coins&money)))]) (def t [[10,5,1,25] [1,10] [10,5,1,0] [10,5,1,-1]]) (defn compute ;;计算当前参数列表序列的结果 ;;[[10 5 1 25] [1 10] [10 5 1 0] [10 5 1 -1]] -> ([10 5 1 25] 1 1 0) [holder] (map finish? holder)) (defn get-cur-r ;;从当前的计算结果中取得全部兑换结束的结果 ;;([10 5 1 25] 1 1 0) -> 2 [compr] (apply + (filter #(not (coll? %)) compr))) (defn get-cur-col ;;保留当前计算结果中未兑换完的参数列表 ;;([10 5 1 25] 1 1 0) -> ([10 5 1 25]) [combs] (filter coll? combs)) (defn change ;;主要的兑换过程 ;; coins array of coin kinds [50 25 10 5 1] ;; money money to change n [coins money] (let [cm (conj coins money)] ;;[50 25 10 5 1 100] (loop [holder [cm] ;;[[50 25 10 5 1 100]] result 0] ;;0 (if (empty? holder) result (let [[f & rest] holder ;;f: [25,10,5,1,100] rest: [[50,25,10,5,1,50]] h (change-helper f);; [[10,5,1,100] [25 10 5 1 75]] h1 (compute h) ;; ([10,5,1,100] [25 10 5 1 75]) c (get-cur-col h1);; [[10,5,1,100] [25 10 5 1 75]] r (get-cur-r h1)];; 0 (recur (into rest c) (+ result r))))))) (def coins [50 25 10 5 1]) (def money 100) (def cm [(conj coins money)]) ;;(prn cm) (def h (change-helper (first cm))) ;;(prn h) (def h1 (compute h)) ;;(prn h1) (def c (get-cur-col h1)) ;;(prn c) (def r (get-cur-r h1)) ;;(prn r) (change coins money) ;;292
我没有验证该方法的正确性,只验证了100元的兑换方案为292种,500元有59576种,800元有343145种,1000元有801451种,若是你也实现了,还请帮我验证一下
1000元的,若是使用递归方法,可能就不行了
谢谢rest