js算法题解(第26天)---leetcode 70. 爬楼梯

199 阅读4分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情

前言

每天一道算法题,死磕算法

今天我们来讲动态规划,终于到了动态规划这一章了,这一章是最好玩的,五花八门的各种题型

什么杨辉三角,打家劫舍,爬楼梯等,让你感觉像小时候的奥赛数学题

这一章我们要多讲一些,因为动态规划的题目千变万化,大家要注意多总结

题目

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入: n = 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶

思考

第一步:从题目中提取关键字

  • 这道题让我们求所有的方法个数(动态规划题目一定要弄懂让求什么,不要一上来就套公式)

第二步:分析

step1:递归求解

那么我们可以得出

  • 1层:1种方法
  • 2层:1+1,2 2种方法
  • 3层:1+1+1,2+1,1+2 3中方法
  • 4层:1+1+1+1,2+1+1,1+2+1,1+1+2,2+2 5中方法 ......

所以我们可以简单得出f(n)=f(n-1)+f(n-2)的结论,就像我们小时候找规律,先从小的范围找规律,在用其他数验证

那么f(n-1)等于多少呢f(n-1)=f(n-2)+f(n-3),
那f(n-2)呢f(n-2)=f(n-3)+f(n-4)
.....
直到f(3)=f(2)+f(1),f(2)=f(2),f(1)=f(1)
也就是f(2)和f(1)是最底层了,不能在分裂了

那我们画张图表示一下这个关系吧

image.png

这不就是一颗二叉树么,想起二叉树我们想起什么,递归呀

所以我们随手就写了一个递归来结此题

var climbStairs = function(n) {
  if(n === 2){
    return 2;
  }
  if(n === 1){
    return 1;
  }
  return climbStairs(n-1) + climbStairs(n-2);
};

但是我们这种解法放到leetcode中,发现超出时间限制,这是为什么呢?

step2:记忆化搜索法

因为我们有很多重复的解

image.png

我们看一下图中标红的地方,是不是被重复计算的地方

比如f(9) = f(8)+f(7)
我们把f(8)拆解一下,那么f(9)又等于f(7)+f(6)+f(7),是不是重复计算了两次f(7),那我们怎么办呢,我们可以把值保存起来呀,保存到数组中

所以我们又来了一版

let arr = [];
var climbStairs = function(n) {
  if(n === 2){
    return 2;
  }
  if(n === 1){
    return 1;
  }
  if(!arr[n]){
    arr[n] = climbStairs(n-1) + climbStairs(n-2);
  }
  return arr[n];
};

记忆搜索转化为动态规划

我们上面是一个自顶向下的过程,而动态规划恰恰相反,是一个自底向上的过程,也就是根据已知条件,迭代出来结果


const climbStairs = function(n) {
    // 初始化状态数组
    const f = [];
    // 初始化已知值
    f[1] = 1;
    f[2] = 2;
    // 动态更新每一层楼梯对应的结果
    for(let i = 3;i <= n;i++){
        f[i] = f[i-2] + f[i-1];
    }
    // 返回目标值
    return f[n];
};

总结

总结两点什么样的题目往动态规划上想

最优子结构:你想求得的最优的答案,其实也是子结构的最优答案,这一点在接下来的找硬币中体现的非常明显,那么你就把最底层的子结构的解求出来,看他和文中给的条件之间有什么联系 重叠子问题:就是一道题目中,要计算最终结果,那么需要子问题累计起来,但是子问题会重复,就像上面标红的图片一样,会有重复计算的部分

动态规划的难点在哪里?

状态转移方程:这个不好想,有个技巧那就是动态规划一定是把每一个子结构都要求出来,所以你先从最底层子结构入手,看他们和条件有什么关系,然后大胆推想 递归转迭代:动态规划中的递归都会有重叠子问题,所以要想转化成动态规划的话,那么需要转化成迭代的方法

参考