如今有若干不一樣面額的零錢,供顧客來換。零錢種類有 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