动态规划 详解

动态规划(Dynamic Programming, DP)详解

动态规划是一种通过分解问题为子问题并利用子问题的解来解决原问题的算法设计方法。它通常用于解决具有 重叠子问题最优子结构 性质的问题。


1. 动态规划的核心思想

1.1 重叠子问题

  • 问题可以分解为多个子问题,且这些子问题会重复出现。
  • 动态规划通过 记忆化(Memoization)表格(Tabulation) 的方式保存子问题的解,避免重复计算。

示例:斐波那契数列

  • 递归法会重复计算大量子问题,如:
    F ( 5 ) = F ( 4 ) + F ( 3 ) F(5) = F(4) + F(3) F(5)=F(4)+F(3)
    其中:
    F ( 4 ) = F ( 3 ) + F ( 2 ) F(4) = F(3) + F(2) F(4)=F(3)+F(2)
    可以看到,F(3) 被重复计算。

1.2 最优子结构

  • 一个问题的最优解可以通过其子问题的最优解推导得到。
  • 如果问题不满足最优子结构,动态规划无法使用。

示例:最短路径问题

  • 若从点 A 到点 C 的最短路径经过点 B,则从 A 到 B 和 B 到 C 的路径也必须是最短的。

1.3 状态转移

  • 动态规划通过状态转移方程来定义子问题之间的关系。

示例:斐波那契数列

  • 递归公式(状态转移方程): F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n) = F(n-1) + F(n-2) F(n)=F(n−1)+F(n−2)

2. 动态规划的解题步骤

动态规划一般按照以下步骤来解决问题:

2.1 明确状态

  • 定义一个数组或变量表示问题的状态。
  • 状态表示 是动态规划的核心,通常需要回答:
    • 子问题是什么?
    • 如何通过状态表示子问题?

示例:最长上升子序列

  • 状态定义:dp[i] 表示以第 i 个元素结尾的最长上升子序列的长度。

2.2 确定状态转移方程

  • 状态转移方程描述了当前状态如何由之前的状态推导而来。

示例:最长上升子序列

  • 转移方程:若第 i 个元素大于第 j 个元素:
    d p i = max ⁡ ( d p i , d p j + 1 ) ( j < i ) dpi = \max(dpi, dpj + 1) \quad (j < i) dpi=max(dpi,dpj+1)(j<i)

2.3 初始化

  • 为边界状态赋初值,通常与问题的初始条件对应。

示例:最长上升子序列

  • 初始状态:每个元素单独构成一个序列:
    d p i = 1 dpi = 1 dpi=1

2.4 计算顺序

  • 通过遍历、递推等方法计算所有子问题的解。
  • 通常选择从小到大的顺序来保证子问题已经被计算。

2.5 返回最终结果

  • 根据问题要求返回最终结果,通常是 dp 数组中的某个值或整体。

3. 动态规划的两种实现方式

3.1 自顶向下(记忆化递归)

  • 类似于递归,将大问题分解为小问题,先解决需要的子问题。
  • 使用缓存(如数组或哈希表)保存已计算过的子问题结果。

示例:斐波那契数列

java 复制代码
public class Fibonacci {
    private static int[] memo;

    public static int fib(int n) {
        if (n <= 1) return n;

        // 检查是否已经计算过
        if (memo[n] != -1) return memo[n];

        // 递归计算,并存储结果
        memo[n] = fib(n - 1) + fib(n - 2);
        return memo[n];
    }

    public static void main(String[] args) {
        int n = 10;
        memo = new int[n + 1];
        Arrays.fill(memo, -1);
        System.out.println(fib(n)); // 输出:55
    }
}

3.2 自底向上(迭代法)

  • 先解决最小子问题,再逐步推导大问题。
  • 使用表格存储子问题的解,按顺序填表。

示例:斐波那契数列

java 复制代码
public class Fibonacci {
    public static int fib(int n) {
        if (n <= 1) return n;

        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;

        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }

    public static void main(String[] args) {
        System.out.println(fib(10)); // 输出:55
    }
}

4. 动态规划的分类

4.1 一维动态规划

  • 问题状态只与一个变量有关。
  • 例子:斐波那契数列、爬楼梯问题。

4.2 二维动态规划

  • 问题状态与两个变量有关(如二维表格问题)。
  • 例子:最长公共子序列(LCS)、编辑距离(Levenshtein Distance)。

4.3 背包问题

  • 0-1 背包问题: 选择每个物品时要么拿,要么不拿。
  • 完全背包问题: 每个物品可以选无限次。
  • 多重背包问题: 每个物品有一个选择次数限制。

4.4 区间动态规划

  • 问题状态与区间的起点和终点有关。
  • 例子:矩阵链乘法、戳气球问题。

4.5 状态压缩动态规划

  • 用位运算压缩多个状态。
  • 例子:旅行商问题(TSP)。

5. 常见动态规划问题及解法

5.1 斐波那契数列

状态转移方程:
F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n) = F(n-1) + F(n-2) F(n)=F(n−1)+F(n−2)


5.2 爬楼梯问题

  • 每次可以爬 1 或 2 个台阶,求到达第 n 级台阶的总方法数。
    状态转移方程:
    d p i = d p i − 1 + d p i − 2 dpi = dpi-1 + dpi-2 dpi=dpi−1+dpi−2

5.3 最长公共子序列

  • 给定两个字符串,求它们的最长公共子序列。
    状态转移方程:
    d p i j = { d p i − 1 j − 1 + 1 if A i = B j max ⁡ ( d p i − 1 j , d p i j − 1 ) otherwise dpij = \begin{cases} dpi-1j-1 + 1 & \text{if } Ai = Bj \\ \max(dpi-1j, dpij-1) & \text{otherwise} \end{cases} dpij={dpi−1j−1+1max(dpi−1j,dpij−1)if Ai=Bjotherwise

5.4 0-1 背包问题

  • 给定物品重量和价值,选择部分物品放入背包,最大化总价值。
    状态转移方程:
    d p i w = max ⁡ ( d p i − 1 w , d p i − 1 w − weight \[ i ] + value i ) dpiw = \max(dpi-1w, dpi-1w-\\text{weight}\[i] + \text{value}i) dpiw=max(dpi−1w,dpi−1w−weight\[i]+valuei)

6. 总结

动态规划的核心是通过分治和记忆化避免重复计算,适用于具有 重叠子问题最优子结构 的问题。以下是使用动态规划时需要注意的关键点:

  1. 确定状态表示和状态转移方程。
  2. 明确子问题之间的依赖关系。
  3. 选择合适的实现方式(递归+记忆化 或 迭代+表格)。
  4. 对问题规模和时间复杂度进行优化(如滚动数组优化空间复杂度)。
相关推荐
aini_lovee几秒前
FMCW雷达测速测距系统(锯齿波 + CFAR检测)
算法
qq_297574674 分钟前
设计模式系列文章(基础篇第 11 篇):模板方法模式——定义算法骨架,实现代码复用与流程统一
算法·设计模式·模板方法模式
lqqjuly11 分钟前
知识蒸馏:理论、算法与可运行实现
人工智能·深度学习·算法
水上冰石20 分钟前
comfui的sd1.5模型,有多少采样算法,详解每一个采样算法
人工智能·算法
黎阳之光38 分钟前
视频孪生+空天地水工融合,黎阳之光构建智慧水利监测新范式
大数据·人工智能·物联网·算法·安全
cheems95271 小时前
[算法手记] 贪心 爬楼梯问题
算法·贪心算法
KaMeidebaby1 小时前
卡梅德生物技术快报|酵母双杂交 cDNA 文库构建与蛋白互作筛选流程
服务器·前端·数据库·人工智能·算法
sheeta19981 小时前
LeetCode 每日一题笔记 日期:2026.05.27 题目:3121. 统计特殊字母的数量 II
笔记·算法·leetcode
ST——Jess1 小时前
年度行业趋势研究报告:泛心理数字化赛道“流日推演”的算法困境与高保真交互范式重构
人工智能·算法·架构
Tisfy1 小时前
LeetCode 3300.替换为数位和以后的最小元素:一次遍历
数学·算法·leetcode·模拟