问题:
爬n阶楼梯,每次只能走1阶或者2阶,计算有多少种走法。
暴力计算+记忆化递归。
从位置 i 出发,每次走1阶或者2阶台阶,记录从位置 i 出发到目标 n 所有的走法数量,memoA[i] 。记录的数据可以重复使用,避免冗余计算。
时间复杂度:O(n)。每次计算从 i 位置出发到终点 n 的走法,共计算 n 次,即树形递归的大小为n。
空间复杂度:O(n)。使用了长度为 n 的数组。
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]; } };
动态规划
动态规划的关键步骤在于构建递归函数。由于第n级阶梯可由第n-1级(跨一步)和第n-2级(跨两步)到达。记 f(n) 为到达第n级阶梯的方案数量,则有递归函数 f(n) = f(n-1)+f(n-2).
1)动态规划第一种实现方法:
时间复杂度:O(2^n)。其运算过程就是一个完全二叉树的展开,共有 2^(n-2)+1 个节点,即递归运算了 2^(n-2)+1 次。
空间复杂度: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个斐波那契数,而非进行遍历计算。
这就是算法的魅力。从暴力求解开始,逐步优化算法流程,砍掉冗余的计算步骤,尽可能去除遍历步骤。最终达到提升时间复杂度和空间复杂度的目的。