动态规划从入门到LeetCode

简介: 动态规划从入门到LeetCode

前言

算法好像已经断更了好久, 十月份没控制住自己, 玩了一整个月, 今天开始争取补回来, 「代码随想录·算法训练营」已经开始第 48 天了, 把打卡表格缩小到 30% 之后才看到我上次打卡的内容, 现在已经开始动态规划部分了, 准备从第 38 天开始追, (Ps: day38 是本篇文章, 也是动态规划的基础题目)

网络异常,图片无法展示
|

今日任务:

动态规划理论基础

什么是动态规划?

某一问题可以分解为很多重叠的子问题,而这些子问题之间存在着递进的状态推导关系。对这类问题的求解方法称为动态规划

动态规划五部曲

  • 确定 dp 数组以及下标的含义
  • 确定递推公式
  • dp 数组如何初始化
  • 确认遍历顺序
  • 举例推导 dp 数组

重中之重: 如果你看了题解, 也模仿着写出来了, 但是就是通过不了, 那么一定要思考三个问题

  • 这道题目我举例推导状态转移公式了么?
  • 我打印dp数组的日志了么?
  • 打印出来了dp数组和我想的一样么?

509. 斐波那契数

题目描述

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

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
复制代码

给定 n ,请计算 F(n)

 

示例 1:

输入: n = 2
输出: 1
解释: F(2) = F(1) + F(0) = 1 + 0 = 1
复制代码

示例 2:

输入: n = 3
输出: 2
解释: F(3) = F(2) + F(1) = 1 + 1 = 2
复制代码

示例 3:

输入: n = 4
输出: 3
解释: F(4) = F(3) + F(2) = 2 + 1 = 3
复制代码

 

提示:

  • 0 <= n <= 30

思路分析

  • 确定 dp 数组以及下标的含义
  • dp[i] 的定义为: 第 i 个数的斐波那契数值为 dp[i]
  • 确定递推公式
  • 题中提供了递推公式: dp[i] = dp[i-1] + dp[i-2]
  • dp 数组如何初始化
  • dp[0] = 0, dp[1] = 1
  • 确认遍历顺序
  • 根据本题的递推公式 dp[i] = dp[i-1] + dp[i-2]  可知, 遍历顺序肯定是从小到大的
  • 举例推导 dp 数组
  • 按照递推公式 dp[i] = dp[i-1] + dp[i-2] 进行推导, 假设 n == 10, 那么 dp 数组肯定如下所示
  • 0 1 1 2 3 5 8 13 21 34 55

代码展示

class Solution {
    public int fib(int n) 
        // 当 n<=1 时, 直接返回 n, 不用进行推导
        if (n <= 1){
            return n;
        }
        // 确定 dp 数组大小, 注: n == 10 时, 数组应该有 11 个元素
        int[] ints = new int[n+1];
        // 初始化数组
        ints[0] = 0;
        ints[1] = 1;
        // 遍历计算
        for (int i = 2; i <= n; i++) {
            // 使用推导公式计算 dp 数组
            ints[i] = ints[i - 1] + ints[i - 2];
        }
        // 返回第 n+1 个元素
        return ints[n];
    }
}
复制代码

提交结果

网络异常,图片无法展示
|

错误分析

在第一次解答出错的时候, 是数组设置成了长度为 n, 没有想明白, 也是自信了, 没有自己 debug 走一下

第二, 三次解答出错, 分别是因为数组扩容之后 for 循环忘记 i <= n 了, 还有是 return 返回的时候返回成了 ints[n - 1]

70. 爬楼梯

题目描述

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

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

示例 1:

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

示例 2:

输入: n = 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
复制代码

提示:

  • 1 <= n <= 45

思路分析

依旧根据 动态规划五部曲 进行分析

  • 确定 dp 数组以及下标的含义
  • dp 数组的长度应该是 n, 和台阶数相等, 每一个下标都应该是有多少种不同的方法
  • 递推公式 (这里我好半天才理解明白, 重点)
  • 由题可知, 每次只能跳 1 个台阶或者 2 个台阶, 那么 dp[n] 只可能由 dp[n-1]dp[n-2] 跳上去
  • 所以有 dp[n-1] 种方法和 dp[n-2] 种方法, 跳上 dp[n] 台阶
  • 所以 dp[n] = dp[n-1] + dp[n-2]
  • dp 数组初始化
  • 根据第一步, dp数组及下标含义可知, dp[n] = 第 n 个台阶有多少种方法
  • dp[0] = 0, dp[1] = 1, dp[2] = 2
  • 确认遍历顺序
  • 经过综上分析, 爬楼梯应该是 从前往后 的顺序进行遍历
  • 举例推导 dp 数组
  • 当 n = 10 时, dp 数组的长度应该为 11
  • dp 数组 = [0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

代码展示

public static int climbStairs(int n) {
        if (n < 3){
            return n;
        }
        // 确定 dp 数组长度, 并初始化
        int[] ints = new int[n+1];
        ints[1] = 1;
        ints[2] = 2;
        // 遍历, 从 i = 3 开始, 长度为  n+1
        for (int i = 3; i < n+1; i++) {
            // 使用推导公式, 确定到达每个台阶的方法数
            ints[i] = ints[i-1] + ints[i-2];
        }
        return ints[n];
    }
复制代码

提交结果

网络异常,图片无法展示
|

746. 使用最小花费爬楼梯

题目描述

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

示例 1:

输入: cost = [10,15,20]
输出: 15
解释: 你将从下标为 1 的台阶开始。
- 支付 15 ,向上爬两个台阶,到达楼梯顶部。
总花费为 15 。
复制代码

示例 2:

输入: cost = [1,100,1,1,1,100,1,1,100,1]
输出: 6
解释: 你将从下标为 0 的台阶开始。
- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。
复制代码

提示:

  • 2 <= cost.length <= 1000
  • 0 <= cost[i] <= 999

思路分析

老规矩, 动态规划五部曲

  • 确定 dp 数组以及下标的含义
  • 到达 dp[i] 最少花费的费用
  • 确定递推公式
  • 在本题, 一共有两种方式可以得到 dp[i] 的花费, 一种是通过 dp[i-1], 一种是 dp[i-2]
  • dp[i] = dp[i-1] + cost[i-1] <== 走了一步到达
  • dp[i] = dp[i-2] + cost[i-2] <== 走了两步到达
  • 本题要求得最小花费, 那么 dp[i] = Math.min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
  • dp 数组初始化
  • 由题可知: 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。所以在 dp[0] 和 dp[1] 的情况下是 0 花费的
  • dp[0] = 0
  • dp[1] = 0
  • 举例推导 dp 数组
  • 如下图所示

网络异常,图片无法展示
|

代码展示

public static int minCostClimbingStairs(int[] cost) {
    // 初始化 dp 数组
    int[] dp = new int[cost.length + 1];
    // 遍历为 dp 数组赋值, int 数组默认元素为0
    for (int i = 2; i < dp.length; i++) {
        // 推导公式计算 最低 花费
        dp[i] = Math.min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);
    }
    return dp[cost.length];
}
复制代码

提交结果

网络异常,图片无法展示
|

总结

第一次接触 动态规划 目前脑海中对这种类型的题有了初步的解题思路, 但是研究不出来的时候还是要看一眼题解, 今日三道题用时 90 分钟



目录
相关文章
|
6月前
|
缓存
力扣每日一题 6/14 动态规划+数组
力扣每日一题 6/14 动态规划+数组
41 1
|
6月前
|
存储 算法 数据可视化
深入解析力扣161题:相隔为 1 的编辑距离(逐字符比较与动态规划详解)
深入解析力扣161题:相隔为 1 的编辑距离(逐字符比较与动态规划详解)
|
6月前
|
算法 索引
力扣每日一题 6/28 动态规划/数组
力扣每日一题 6/28 动态规划/数组
55 0
|
6月前
|
存储
力扣每日一题 6/19 排序+动态规划
力扣每日一题 6/19 排序+动态规划
35 0
|
6月前
|
算法
力扣每日一题 6/16 字符串 + 随机一题 动态规划/数学
力扣每日一题 6/16 字符串 + 随机一题 动态规划/数学
48 0
|
6月前
|
机器人
【LeetCode】--- 动态规划 集训(二)
【LeetCode】--- 动态规划 集训(二)
45 0
|
6月前
【LeetCode】--- 动态规划 集训(一)
【LeetCode】--- 动态规划 集训(一)
37 0
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(4)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
|
6月前
|
存储 算法
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总(1)
【42页动态规划学习笔记分享】动态规划核心原理详解及27道LeetCode相关经典题目汇总
|
6月前
|
存储 算法 数据挖掘
力扣174题动态规划:地下城游戏(含模拟面试)
力扣174题动态规划:地下城游戏(含模拟面试)