代码随想录刷题day38|斐波那契数&爬楼梯&最小花费爬楼梯


day38学习内容

day37主要内容

  • 斐波那契数
  • 爬楼梯
    最小花费爬楼梯

声明
本文思路和文字,引用自《代码随想录》

一、动态规划理论基础

1.1、动态规划理论基础的几个关键概念:

  1. 最优子结构(Optimal Substructure):一个问题的最优解包含其子问题的最优解。这意味着我们可以通过组合子问题的最优解来构造问题的最优解。

  2. 边界条件(Boundary Conditions):问题的最小子集,它们是无法再分解的,并且解决方案是显而易见的。这些条件是动态规划算法的起点。

  3. 状态转移方程(State Transition Equation):这是动态规划的核心,定义了从一个状态到另一个状态的转移规则。一个“状态”通常描述了解决问题的某个阶段,而状态转移方程描述了这些阶段如何转化。

  4. 备忘录法(Memoization):一种优化技术,用于通过存储之前计算的结果(即“记忆”这些结果)来避免重复计算。这通常用于递归解决方案中,以提高效率。

  5. 自底向上(Bottom-Up Approach):与递归(通常是自顶向下的)相反,自底向上的方法首先解决子问题,然后合并这些解决方案来解决更大的问题。这通常涉及到使用迭代而非递归,并且使用表格(数组或其他数据结构)来存储中间结果。

1.2、动态规划五部曲

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

二、斐波那契数

509.原题链接

2.1、动态规划五部曲

2.1.1、 确定dp数组(dp table)以及下标的含义

- dp[i]表示第i个数,和为dp[i]

2.1.2、确定递推公式

题意已经告诉你了。dp[i] = dp[i - 1] + dp[i - 2];,不用自己推导了。

2.1.3、 dp数组如何初始化

很明显。dp[0] = 0
dp[1] = 1

2.1.4、确定遍历顺序

从前向后遍历,没啥好说的

2.1.5、计算并返回最终结果

return dp[i]即可

2.2、代码

class Solution {
    public int fib(int n) {
        if (n < 2) {
            return n;
        }
        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;
        for (int i = 2; i < n + 1; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

2.2.1、为什么dp[0] = 0?

看题目。。
在这里插入图片描述


三、爬楼梯

70.原题链接

3.1、动态规划五部曲

3.1.1、 确定dp数组(dp table)以及下标的含义

- dp[i]表示到达第i个楼梯,有dp[i]种方法

3.1.2、确定递推公式

3.1.3、 dp数组如何初始化

很明显。dp[0] = 1
dp[1] = 1

3.1.4、确定遍历顺序

从前向后遍历,没啥好说的

3.1.5、计算并返回最终结果

return dp[i]即可

3.2、代码

class Solution {
    public int climbStairs(int n) {
        if (n < 2) {
            return n;
        }
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

四、最小花费爬楼梯

746.原题链接

4.1、动态规划五部曲

4.1.1、 确定dp数组(dp table)以及下标的含义

- dp[i] 表示到达第i层楼梯,最小成本为dp[i]。

4.1.2、确定递推公式

推导过程

  1. 定义状态:首先,我们定义 dp[i] 为到达第 i 阶楼梯所需的最小成本。

  2. 分析选择:对于每一阶楼梯 i(除了第一和第二阶),我们可以从第 i-1 阶或第 i-2 阶到达。这意味着到达第 i 阶的最小成本取决于:

    • 从第 i-1 阶上来的成本(即 dp[i-1] 加上 cost[i]),以及
    • 从第 i-2 阶上来的成本(即 dp[i-2] 加上 cost[i])。
  3. 确定状态转移方程:因此,为了计算 dp[i],我们需要选择这两种方式中成本更低的一种,即:
    dp[i] = min(dp[i-1], dp[i-2]) + cost[i]
    这里 dp[i-1] + cost[i] 代表了通过先支付到达第 i-1 阶的成本,再支付第 i 阶的成本来到达第 i 阶的总成本。类似地,dp[i-2] + cost[i] 代表了先支付到达第 i-2 阶的成本,再支付第 i 阶的成本的总成本。

4.1.3、 dp数组如何初始化

很明显。对于初始阶梯 dp[0]dp[1],因为没有前面的阶梯可以选择,所以它们的成本直接等于 cost[0]cost[1]

4.1.4、确定遍历顺序

从前向后遍历,没啥好说的

4.1.5、计算并返回最终结果

返回达到倒数第一阶和倒数第二阶中最小成本的那一个,因为这两个位置都可以直接到达楼梯顶端。
return Math.min(dp[cost.length - 1], dp[cost.length - 2]);
return Math.min(dp[cost.length - 1], dp[cost.length - 2])为什么不用加cost[i]

因为

  1. 问题定义:数组 cost 中的每个元素代表爬上对应阶梯的成本。一旦支付了爬上某阶梯的成本,你就可以站在那一阶上。而目标是以最小的成本到达楼梯的顶部,顶部位于 cost 数组之外的位置。

  2. 最终目标:考虑到你可以一次爬一阶或两阶,到达顶部的最后一步可以从倒数第一阶开始,也可以从倒数第二阶开始。这意味着在实际达到楼梯顶部时,并不需要支付额外的成本,因为你已经站在了可以直接到达顶部的阶梯上。

  3. 成本已包括:当计算 dp 数组时,每个 dp[i] 已经包含了到达第 i 阶所需的全部成本,包括爬上第 i 阶的成本。所以,到达倒数第一阶或倒数第二阶的成本已经计算在内,不需要再额外添加成本来完成最后一步的动作。

综上所述,选择 dp[cost.length - 1]dp[cost.length - 2] 中的最小值作为最终结果,是因为这两个值已经反映了到达楼梯顶部(实际是站在能够一步到达顶部的最后两阶楼梯上)的全部必要成本。这个设计巧妙地避免了在问题解决方案的最后一步中不必要的成本计算,直接利用了动态规划数组中的结果来找出达到楼梯顶部的最小成本。

4.2、代码

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int[] dp = new int[cost.length];
        dp[0] = cost[0];
        dp[1] = cost[1];
        for (int i = 2; i < cost.length; i++) {
            dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i];
        }
        return Math.min(dp[cost.length - 1], dp[cost.length - 2]);
    }
}

总结

1.感想

  • 动态规划的第一天,加油

2.思维导图

本文思路引用自代码随想录,感谢代码随想录作者。-

  • 29
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值