动态规划入门

一、动态规划的核心概念

动态规划(Dynamic Programming,简称 DP)是一种将复杂问题拆分为重叠子问题,通过存储子问题的解来避免重复计算 的算法思想。它的核心是 "状态转移" ------ 用子问题的最优解推导原问题的最优解,和贪心算法的 "局部最优→全局最优" 不同,DP 依赖子问题的递推关系,适用于有重叠子问题和最优子结构的场景。

动态规划的三大核心要素

  1. 状态定义 :确定 dp[i] 表示什么含义(这是 DP 的灵魂,定义错了后续全错)。
  2. 状态转移方程 :建立 dp[i]dp[i-1]dp[i-2] 等前面状态的关系(递推公式)。
  3. 初始化与边界条件 :确定 dp 数组的初始值(比如 dp[0]dp[1]),避免递推时越界。

动态规划五部曲(解题方法论)

  1. 确定 dp 数组及下标的含义
  2. 确定递推公式
  3. dp 数组如何初始化
  4. 确定遍历顺序
  5. 举例推导 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. 确定 dp 数组及下标的含义 dp[i] 表示第 i 个斐波那契数的值。
  2. 确定递推公式 题目直接给出:dp[i] = dp[i-1] + dp[i-2]
  3. dp 数组初始化 dp[0] = 0dp[1] = 1
  4. 确定遍历顺序2n 顺序遍历(因为计算 dp[i] 需要前面的 dp[i-1]dp[i-2])。
  5. 举例推导 dp 数组n=5 时:dp[0]=0, dp[1]=1, dp[2]=1, dp[3]=2, dp[4]=3, dp[5]=5

完整 Java 代码

java 复制代码
class Solution {
    public int fib(int n) {
        // 边界条件:n=0或1直接返回
        if (n <= 1) {
            return n;
        }
        // 1. 定义dp数组
        int[] dp = new int[n + 1];
        // 2. 初始化
        dp[0] = 0;
        dp[1] = 1;
        // 3. 遍历顺序:从2到n
        for (int i = 2; i <= n; i++) {
            // 4. 递推公式
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

优化(空间复杂度 O (1))

由于 dp[i] 只依赖前两个值,无需存储整个数组,用两个变量滚动更新即可:

java 复制代码
class Solution {
    public int fib(int n) {
        if (n <= 1) return n;
        int a = 0, b = 1;
        for (int i = 2; i <= n; i++) {
            int c = a + b;
            a = b;
            b = c;
        }
        return b;
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:优化前 O(n),优化后 O(1)

三、70. 爬楼梯

题目描述

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

解题思路(动态规划五部曲)

  1. 确定 dp 数组及下标的含义 dp[i] 表示爬到第 i 阶楼梯的不同方法数。
  2. 确定递推公式 要爬到第 i 阶,有两种选择:
    • 从第 i-1 阶爬 1 步上来
    • 从第 i-2 阶爬 2 步上来因此递推公式:dp[i] = dp[i-1] + dp[i-2]
  3. dp 数组初始化
    • dp[1] = 1(1 阶楼梯只有 1 种方法:爬 1 步)
    • dp[2] = 2(2 阶楼梯有 2 种方法:1+1 或 2)
  4. 确定遍历顺序3n 顺序遍历。
  5. 举例推导 dp 数组n=5 时:dp[1]=1, dp[2]=2, dp[3]=3, dp[4]=5, dp[5]=8

注意:本题本质就是斐波那契数列的变形,区别仅在于初始化值不同。

完整 Java 代码

java 复制代码
class Solution {
    public int climbStairs(int n) {
        // 边界条件处理
        if (n <= 2) {
            return n;
        }
        int[] dp = new int[n + 1];
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

优化(空间复杂度 O (1))

java 复制代码
class Solution {
    public int climbStairs(int n) {
        if (n <= 2) return n;
        int a = 1, b = 2;
        for (int i = 3; i <= n; i++) {
            int c = a + b;
            a = b;
            b = c;
        }
        return b;
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:优化前 O(n),优化后 O(1)

四、746. 使用最小花费爬楼梯

题目描述

给你一个整数数组 cost,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬 1 个或者 2 个台阶。你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。

解题思路(动态规划五部曲)

  1. 确定 dp 数组及下标的含义 dp[i] 表示到达第 i 阶台阶时的最低花费
  2. 确定递推公式 到达第 i 阶有两种途径:
    • i-1 阶爬上来,花费 dp[i-1] + cost[i-1]
    • i-2 阶爬上来,花费 dp[i-2] + cost[i-2]取两者最小值:dp[i] = Math.min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
  3. dp 数组初始化
    • dp[0] = 0(从第 0 阶开始,无需花费)
    • dp[1] = 0(从第 1 阶开始,无需花费)
  4. 确定遍历顺序2n 顺序遍历(n 是楼梯顶部,对应 cost 数组长度)。
  5. 举例推导 dp 数组 假设 cost = [10, 15, 20]n=3(顶部是第 3 阶):
    • dp[0] = 0dp[1] = 0
    • dp[2] = min(0+10, 0+15) = 10
    • dp[3] = min(10+15, 0+20) = 20最终答案是 20

完整 Java 代码

java 复制代码
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        int[] dp = new int[n + 1];
        // 初始化:从0或1阶开始,花费为0
        dp[0] = 0;
        dp[1] = 0;
        // 遍历顺序:从2到n
        for (int i = 2; i <= n; i++) {
            dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }
        return dp[n];
    }
}

优化(空间复杂度 O (1))

java 复制代码
class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        int a = 0, b = 0;
        for (int i = 2; i <= n; i++) {
            int c = Math.min(b + cost[i-1], a + cost[i-2]);
            a = b;
            b = c;
        }
        return b;
    }
}

复杂度分析

  • 时间复杂度:O(n)
  • 空间复杂度:优化前 O(n),优化后 O(1)

总结

  1. 动态规划的核心:状态定义 + 状态转移方程,入门题的状态定义都比较直观,重点是找到递推关系。
  2. 三道题的关联:爬楼梯是斐波那契的变形,最小花费爬楼梯是爬楼梯的 "加权版",递推逻辑一脉相承。
  3. 优化技巧 :当 dp[i] 只依赖前 k 个状态时,可使用滚动变量代替数组,降低空间复杂度。
相关推荐
mjhcsp2 小时前
P14987 全等(mjhcsp)
算法·题解·洛谷
(❁´◡`❁)Jimmy(❁´◡`❁)2 小时前
Atcoder abc441A~F 题解
算法·深度优先·图论
少林码僧2 小时前
2.30 传统行业预测神器:为什么GBDT系列算法在企业中最受欢迎
开发语言·人工智能·算法·机器学习·ai·数据分析
豆沙沙包?2 小时前
2026年--Lc343-1926. 迷宫中离入口最近的出口(图 - 广度优先搜索)--java版
java·算法·宽度优先
超级大福宝3 小时前
【力扣200. 岛屿数量】的一种错误解法(BFS)
数据结构·c++·算法·leetcode·广度优先
独自破碎E3 小时前
【动态规划=递归+记忆化存储】跳台阶
算法·动态规划
一颗青果3 小时前
auto | 尾置返回类型 | decltype | using | typedef
java·开发语言·算法
郝学胜-神的一滴3 小时前
何友院士《人工智能发展前沿》全景解读:从理论基石到产业变革
人工智能·python·深度学习·算法·机器学习
BHXDML3 小时前
第五章:支持向量机
算法·机器学习·支持向量机