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

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

本文将以力扣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问题!🚀

相关推荐
程序员阿鹏19 分钟前
为什么MySQL InnoDB选择B+树?
数据结构·数据库·b树·sql·mysql·算法·缓存
AI 思录41 分钟前
Prompt 事故档案(八):日常表达被标为“待校准”,AI 的爹味语法从哪里来
大数据·人工智能·算法·prompt·用户体验·ai合规
月光船幽幽1 小时前
跨范式映射的稳定接口设计
人工智能·python·算法
信誓旦旦的程序猿1 小时前
【量化系统从零构建 #04】存储设计:选型·建库·交易日历
java·人工智能·python·股票数据api·股票数据·股票数据api接口·股票api数据接口
yurenpai(27届找实习中)1 小时前
Java 10 的 var 为什么不能随便用?从订单汇总看懂类型推断与泛型陷阱
java·开发语言·java18
Flynt2 小时前
把公司项目迁到 Spring Boot 4.0:编译通过只是开始
java·spring boot·后端
2601_965742222 小时前
全媒体运营与短视频代运营,两者有什么区别?
大数据·数据结构·人工智能·算法·ai·媒体
Tangyuewei3 小时前
给老 Spring 项目装个 AI Agent
java·人工智能·spring
她的男孩3 小时前
多租户和数据权限怎么共存?扒完拦截器注册链路,我找到 4 个隐蔽的坑
java·后端·架构
kyriewen3 小时前
我用 AI 写完一个需求后才发现,最难的不是 prompt,而是验收
前端·程序员·ai编程