算法精讲--动态规划四步法则

🚀 动态规划四步法则深度解析(以编辑距离为例)

本文将以力扣72题编辑距离为例,完整演示动态规划四步法则的应用过程。


一、🔍 审题分析(问题建模)

🔧 操作限制:允许三种原子操作

  • 插入一个字符(耗时1单位)
  • 删除一个字符(耗时1单位)
  • 替换一个字符(耗时1单位)

🎯 所求目标 :将字符串A转换为字符串B的最小操作次数

⚠️ 特殊边界

  • 当A为空时,操作次数等于B的长度(全插入)
  • 当B为空时,操作次数等于A的长度(全删除)

二、📐 方案设计(三要素建模)

2.1 状态定义(阶段与状态变量)

java 复制代码
// dp[i][j]表示将A的前i个字符转换为B的前j个字符的最小代价
int[][] dp = new int[m+1][n+1];  // m=A.length(), n=B.length()

阶段划分

  • 每个字符处理视为一个阶段,总阶段数为max(m,n)
  • 状态变量 (i,j)表示处理到A的第i个字符和B的第j个字符的状态

2.2 转移方程推导(决策过程)

java 复制代码
if (A.charAt(i-1) == B.charAt(j-1)) {
    dp[i][j] = dp[i-1][j-1];  // 字符相同无需操作
} else {
    // 三种决策取最小值
    dp[i][j] = Math.min(
        dp[i-1][j] + 1,    // 删除A[i](进入i-1阶段)
        Math.min(
            dp[i][j-1] + 1,    // 插入B[j](进入j-1阶段)
            dp[i-1][j-1] + 1   // 替换字符(同时进入i-1,j-1阶段)
        )
    );
}

决策依据

  • 删除操作:消除A的当前字符,状态回退到 i-1
  • 插入操作:添加B需要的字符,状态推进到 j-1
  • 替换操作:修改字符后双方同时推进

2.3 边界条件设置

java 复制代码
// 初始化第一行(A为空,全插入操作)
for (int j=0; j<=n; j++) dp[0][j] = j;

// 初始化第一列(B为空,全删除操作)
for (int i=0; i<=m; i++) dp[i][0] = i;

三、🔬 执行验证(代码实现)

3.1 基础版实现

java 复制代码
public int minDistance(String word1, String word2) {
    int m = word1.length(), n = word2.length();
    int[][] dp = new int[m+1][n+1];
  
    // 边界初始化
    for (int i=0; i<=m; i++) dp[i][0] = i;
    for (int j=0; j<=n; j++) dp[0][j] = j;
  
    // 状态转移
    for (int i=1; i<=m; i++) {
        for (int j=1; j<=n; j++) {
            if (word1.charAt(i-1) == word2.charAt(j-1)) {
                dp[i][j] = dp[i-1][j-1];
            } else {
                dp[i][j] = Math.min(Math.min(
                    dp[i-1][j], 
                    dp[i][j-1]), 
                    dp[i-1][j-1]
                ) + 1;
            }
        }
    }
    return dp[m][n];
}

3.2 测试案例验证

java 复制代码
// 测试案例1:leetcode -> 3次操作(替换l→e,替换e→t,删除d)
assert minDistance("horse", "ros") == 3;

// 测试案例2:空字符串处理
assert minDistance("", "algorithm") == 9; 

四、⚡ 总结优化(复杂度分析)

4.1 复杂度对比表

方案 时间复杂度 空间复杂度 适用场景
基础二维DP O(mn) O(mn) 常规规模数据
滚动数组 O(mn) O(n) 单行依赖场景
递归+备忘录 O(mn) O(mn) 树状结构处理

4.2 滚动数组优化

java 复制代码
public int minDistanceOpt(String A, String B) {
    int m = A.length(), n = B.length();
    int[] prev = new int[n+1];
  
    // 初始化第一行
    for (int j=0; j<=n; j++) prev[j] = j;
  
    for (int i=1; i<=m; i++) {
        int[] curr = new int[n+1];
        curr[0] = i;  // 每行第一个元素初始化
        for (int j=1; j<=n; j++) {
            if (A.charAt(i-1) == B.charAt(j-1)) {
                curr[j] = prev[j-1];
            } else {
                curr[j] = Math.min(prev[j], Math.min(curr[j-1], prev[j-1])) + 1;
            }
        }
        prev = curr;  // 滚动更新
    }
    return prev[n];
}

优化原理: 由于状态转移仅依赖上一行和当前行前一个元素,通过两个一维数组交替使用,将空间复杂度从O(mn)降为O(n)


🌟 知识延伸(不同路径问题)

问题描述

62.不同路径:机器人从m×n网格左上角到右下角的路径总数(只能向右/向下移动)

四步法则应用

  1. 审题分析

    • 约束:单方向移动限制
    • 目标:路径总数统计
    • 边界:第一行/列只有1种路径
  2. 方案设计

    java 复制代码
    // dp[i][j] = dp[i-1][j] + dp[i][j-1]
    int[][] dp = new int[m][n];
    
    // 状态压缩版
    int[] dp = new int[n];
    Arrays.fill(dp, 1);
    for (int i=1; i<m; i++) {
        for (int j=1; j<n; j++) {
            dp[j] += dp[j-1];  // 滚动累计
        }
    }
  3. 复杂度对比

    • 基础版:空间O(mn) → 压缩版:空间O(n)
    • 时间复杂度均为O(mn)

📌 关键总结 :动态规划本质是通过状态定义将问题分解为多阶段决策过程,通过保存子问题解避免重复计算。掌握四步法则后,可系统化解决90%的DP问题!🚀

相关推荐
清霜之辰17 分钟前
详解 kotlin 相对 Java 特有的关键字及使用
android·java·kotlin
江沉晚呤时19 分钟前
深入解析策略模式在C#中的应用与实现
java·服务器·开发语言·前端·.netcore
zhglhy19 分钟前
随机森林与决策树
算法·决策树·随机森林
Hamm21 分钟前
如何在TypeScript里使用类封装枚举来实现Java的枚举形参倒置
java·前端·typescript
mikey棒棒棒38 分钟前
使用RabbitMQ实现异步秒杀
java·分布式·rabbitmq·mq
无情的搬砖机器39 分钟前
idea 打不开terminal
java·ide·intellij-idea
avi911141 分钟前
问问lua怎么写DeepSeek,,,,,
java·junit·lua·deepseek
BFT白芙堂44 分钟前
Franka 机器人x Dexterity Gen引领遥操作精细任务新时代
人工智能·算法·机器学习·具身智能·franka机器人·科研机器人·机器人解决方案
Wnq1007244 分钟前
具身机器人中AI(DEEPSEEK)与PLC、驱动器协同发展研究:突破数据困境与指令精确控制(3)
大数据·人工智能·程序员·机器人·aigc·ai编程·智能电视
LuckyLay1 小时前
LeetCode算法题(Go语言实现)_38
算法·leetcode·golang