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

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

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

相关推荐
int型码农1 分钟前
数据结构第八章(一) 插入排序
c语言·数据结构·算法·排序算法·希尔排序
UFIT14 分钟前
NoSQL之redis哨兵
java·前端·算法
喜欢吃燃面15 分钟前
C++刷题:日期模拟(1)
c++·学习·算法
刘 大 望18 分钟前
数据库-联合查询(内连接外连接),子查询,合并查询
java·数据库·sql·mysql
SHERlocked9319 分钟前
CPP 从 0 到 1 完成一个支持 future/promise 的 Windows 异步串口通信库
c++·算法·promise
怀旧,24 分钟前
【数据结构】6. 时间与空间复杂度
java·数据结构·算法
积极向上的向日葵37 分钟前
有效的括号题解
数据结构·算法·
GIS小天43 分钟前
AI+预测3D新模型百十个定位预测+胆码预测+去和尾2025年6月7日第101弹
人工智能·算法·机器学习·彩票
大春儿的试验田1 小时前
Parameter ‘XXX‘ not found. Available parameters are [list, param1]
java
_Itachi__1 小时前
LeetCode 热题 100 74. 搜索二维矩阵
算法·leetcode·矩阵