算法
动态规划

斐波那契数列

斐波那契数列 (opens in a new tab)为例,讲解动态规划的思想。斐波那契数列的定义如下:

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

斐波那契数列的自上而下,使用递归实现,在假设已经求得上面的值的情况下,可以很容易的求得下面的值,但是这种实现方式会有很多重复计算,比如求 F(5) 的时候,需要先求 F(4) 和 F(3),但是求 F(4) 的时候,又需要先求 F(3) 和 F(2),这样就会重复计算 F(3)。这种实现方式我们需要保存已经计算过的值,这样就可以避免重复计算。

function fib(n: number): number {
  const mod = 1000000007;
  // 自上而下,递归
  const memo = new Map();
  function dp(n: number): number {
    // base case
    if (n < 2) {
      return n;
    }
    // 去重
    const memoValue = memo.get(n);
    if (memoValue !== undefined) {
      return memoValue;
    }
    const res = (dp(n - 1) + dp(n - 2)) % mod;
    memo.set(n, res);
    return res;
  }
  return dp(n);
}

因为自上而下是递归,空间复杂度比较高,所以我们可以更进一步使用自下而上使用迭代的方式,从下面的值开始计算,这样还能避免重复计算。

function fib(n: number): number {
  const mod = 1000000007;
  // 自下而上,迭代
  const dp = new Array(n);
  // base case
  dp[0] = 0;
  dp[1] = 1;
  // -------
  for (let i = 2; i <= n; i++) {
    dp[i] = (dp[i - 1] + dp[i - 2]) % mod;
  }
  return dp[n];
}

上面例子的空间复杂度还能更进一步优化,因为我们只需要保存 dp[i-1] 和 dp[i-2],所以我们可以只使用两个变量来保存。下面就是我们经常看到的题解了。

function fib(n: number): number {
  const mod = 1000000007;
  // 自下而上,迭代,滚动窗口
  if (n < 2) return n;
  // 分别代表上面的 dp[i-1] 和 dp[i-2]
  let dp1 = 1,
    dp2 = 0;
  for (let i = 2; i <= n; i++) {
    const dp_i = (dp1 + dp2) % mod;
    dp2 = dp1;
    dp1 = dp_i;
  }
  return dp1;
}