簡介 先來描述一下問題,這裡以下樓梯為例:
有一個人下樓梯,一次可以走一階或兩階,假設總共有N階樓梯,共有幾總走法?例如 N = 3,他可以走 1 + 1 + 1 、1 + 2 或 2 + 1 三種走法。
實做一個程式能夠輸入 N,能回傳共有幾總走法。
演算法 這個問題直接使用 Bottom-Up 可能不容易思考,所以我們先利用遞迴的 Top-Down 思考邏輯來想這個問題,結果我們很容易就可以這樣想:
第N階的走法相當於在 N - 1 階的時候走一階過來,或者在 N - 2 階的時候走兩階過來;換句話說,就是第N階的走法等於N - 1階的走法加上N - 2階的走法。
所以我們就可以寫出遞迴式:
1 2 3 F(N) = F(N - 1) + F(N - 2) F(1) = 1 F(2) = 2
列出來的式子和費波那西數列 (Fibonacci) 還蠻像的,我們可以用不同的方式去實做解這問題,這問題較適合用動態規劃 (Dynamic Programming) 去解,直接用 Divide and Conquer 會重覆計算子問題。由於此問題和費波那西數列相當相似,這邊只簡單實做迭代法 和動態規劃的方式。
迭代法 由於每次新的問題的答案來自於前兩個問題,所以每次運算都保留前兩次的資料即可。如果新問題的答案來自前三個問題,則保留三個,以此類推。
動態規劃 使用遞迴的動態規劃則可以記錄過計算過的部分;另外也可以使用迴圈解的動態規劃,在思路上並不複雜,而且也減少Function call的消耗,這邊就不實做此方式,可參考費波那西數列這篇文章。
語法 C# 迭代法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace DownStairs { class Iterative { public static long DownStairs (int n ) { long v1 = 1 ; long v2 = 2 ; long result = n; for (int i = 3 ; i <= n; ++i) { result = v2 + v1; v1 = v2; v2 = result; } return result; } } }
動態規劃 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace DownStairs { class DynamicProgramming { static Dictionary<int , long > map = new Dictionary<int , long >() { { 1 , 1 }, { 2 , 2 } }; public static long DownStairs (int n ) { if (map.ContainsKey(n)) return map[n]; return map[n] = DownStairs(n - 1 ) + DownStairs(n - 2 ); } } }
Java 迭代法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Iterative { public static long DownStairs (int n) { long v1 = 1 ; long v2 = 2 ; long result = n; for (int i = 2 ; i <= n; ++i) { result = v2 + v1; v1 = v2; v2 = result; } return result; } }
動態規劃 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.util.HashMap;import java.util.Map;public class DynamicProgramming { static Map map = new HashMap () {{ put(1 , 1L ); put(2 , 2L ); }}; public static long DownStairs (int n) { if (map.containsKey(n)) return map.get(n); map.put(n, DownStairs(n - 1 ) + DownStairs(n - 2 )); return map.get(n); } }
C++ 迭代法 Iterative.h
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef ITERATIVE_H #define ITERATIVE_H class Iterative { public : static long DownStairs (int n) ; protected : private : }; #endif
Iterative.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include "Iterative.h" long Iterative::DownStairs (int n) { long v1 = 1 ; long v2 = 2 ; long result = n; for (int i = 3 ; i <= n; ++i) { result = v2 + v1; v1 = v2; v2 = result; } return result; }
動態規劃 DynamicProgramming.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #ifndef DYNAMICPROGRAMMING_H #define DYNAMICPROGRAMMING_H #include <map> using namespace std;class DynamicProgramming { public : static long DownStairs (int n) ; protected : private : static pair init[]; static map m; }; #endif
DynamicProgramming.cpp
1 2 3 4 5 6 7 8 9 10 #include "DynamicProgramming.h" pair DynamicProgramming::init[] = { make_pair (1 , 1 ), make_pair (2 , 2 ) }; map DynamicProgramming::m (DynamicProgramming::init, DynamicProgramming::init + 2 ) ;long DynamicProgramming::DownStairs (int n) { if (DynamicProgramming::m.count (n) == 1 ) return DynamicProgramming::m[n]; return DynamicProgramming::m[n] = DynamicProgramming::DownStairs (n - 1 ) + DynamicProgramming::DownStairs (n - 2 ); }
效能實測部分同樣可參考費波那西數列一文。
延伸閱讀 本文的標題叫做上下樓梯問題 & (籃球) 得分問題,到底跟分數有甚麼關係?其實得分問題本質上和上下樓梯是一樣的,只是換不同描述方式,通常會以籃球比賽為例:
籃球比賽可能的得分方式有一分球、兩分球和三分球,得 N 分的方式有幾種?
看完題目描述之後就可以看出來是差不多的,只是組合的方式變更多種。
動態規劃 (Dynamic Programming)