問題:算法
爬n階樓梯,每次只能走1階或者2階,計算有多少種走法。數組
暴力計算+記憶化遞歸。函數
從位置 i 出發,每次走1階或者2階臺階,記錄從位置 i 出發到目標 n 全部的走法數量,memoA[i] 。記錄的數據能夠重複使用,避免冗餘計算。優化
時間複雜度:O(n)。每次計算從 i 位置出發到終點 n 的走法,共計算 n 次,即樹形遞歸的大小爲n。spa
空間複雜度:O(n)。使用了長度爲 n 的數組。code
clock_t start1, end1; class Solution { public: int climbStairs(int n) { int memo[n+1] = {0}; start1 = clock(); int result = climb_stairs(0, n, memo); end1 = clock(); cout << "cost time = " << (double)(end1 - start1) / CLOCKS_PER_SEC << endl; return result; } int climb_stairs(int i, int nums, int memoA[]) { if (i > nums) return 0; if (i == nums) return 1; if (memoA[i] > 0) return memoA[i]; memoA[i] = climb_stairs(i+1, nums, memoA) + climb_stairs(i+2, nums, memoA); return memoA[i]; } };
動態規劃blog
動態規劃的關鍵步驟在於構建遞歸函數。因爲第n級階梯可由第n-1級(跨一步)和第n-2級(跨兩步)到達。記 f(n) 爲到達第n級階梯的方案數量,則有遞歸函數 f(n) = f(n-1)+f(n-2).遞歸
1)動態規劃第一種實現方法:leetcode
時間複雜度:O(2^n)。其運算過程就是一個徹底二叉樹的展開,共有 2^(n-2)+1 個節點,即遞歸運算了 2^(n-2)+1 次。get
空間複雜度:O(n)。遞歸深度達到n層。
class Solution { public: int climbStairs(int n) { if (n == 1) return 1; if (n == 2) return 2; int sumMethod = climbStairs(n-1) + climbStairs(n-2); return sumMethod; } };
2)動態規劃第二種實現方法:
時間複雜度:O(n)
空間複雜度:O(n)
class Solution { public: int climbStairs(int n) { if (n == 1) return 1; int memo[n+1] = {0}; memo[1] = 1; memo[2] = 2; for(int i = 3; i <= n; ++i) { memo[i] = memo[i-1] + memo[i-2]; } return memo[n]; } };
斐波那契數
將動態規劃的第二種實現方法進行修改。第一項爲1,第二項爲2,斐波那契數的計算公式以下:
fib(n) = fib(n-1)+fib(n-2)。
時間複雜度:O(n)
空間複雜度:O(1)
還存在時間複雜度爲 O(log(n)) 的算法,其核心思想爲直接找到第n個斐波那契數,而非進行遍歷計算。
這就是算法的魅力。從暴力求解開始,逐步優化算法流程,砍掉冗餘的計算步驟,儘量去除遍歷步驟。最終達到提高時間複雜度和空間複雜度的目的。