搞定动态规划系列(一):从斐波那契数列入门

147 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

没想到毕业后还能遇到斐波那契数列,不禁想起了那些年被高数支配的恐惧.....但是咱要卷算法,那就卷起来!

它应该算是动态规划的入门题目了,在力扣上找到了对应的题目509,标的是简单,那么就一起探讨下这道题,从而入门动态规划吧。

题目: 斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。

暴力递归

这个题拿到手,第一反应就是暴力递归

function fib (n) {
  return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}

image.png 暴力法解出来是没什么问题哈,自我感觉还代码量极少,但是看到这打败的人数,emmmm...性能太差了,时间复杂度有O(2^N),是指数级别,如果面试时这样写,直接回家等通知吧。那我们试试下一种方案。

递归+记忆化

递归是自顶向下,实质就是存储每次计算过的值,减少重复计算

function fib (n) {
  const map = {}
  
  const sub = function (x) {
    if (map[x]) return map[x]
    if (x === 0) return 0
    if (x === 1) return 1
    map[x] = sub(x-1) + sub(x-2)
    return map[x]
  }
  
  return sub(n)
}

image.png

这种方案由于存储了计算过的值,减少了重复计算,所以复杂度会好一些,但是存储增加了空间复杂度

动态规划

动态规划就不用递归了,用循环自底向上,可省略记忆化过程

function fib (n) {
  let dp = [0, 1]
  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i-1] + dp[i-2]
  }
  return dp[n]
}

image.png 当然,这种方案空间复杂度O(N)仍然存在,时间复杂度O(N)

动态规划的状态压缩

优化上个方案中空间复杂度

function fib (n) {
  if (n <= 1) return n
  let p = 0
  let q = 1
  let r = 1
  for (let i = 2; i <= n; i++) {
     r = p + q
     p = q
     q = r
  }
  return r
}

image.png

用有限的变量存储当前数及上两个数,空间复杂度变为O(1),但这种方案无法记住中间状态的值,对有些场景不适用