AcWing 902. 最短编辑距离(线性DP)

给定两个字符串A和B,现在要将A经过若干操作变为B,可进行的操作有:

  1. 删除–将字符串A中的某个字符删除。
  2. 插入–在字符串A的某个位置插入某个字符。
  3. 替换–将字符串A中的某个字符替换为另一个字符。

现在请你求出,将A变为B至少需要进行多少次操作。

输入格式

第一行包含整数n,表示字符串A的长度。

第二行包含一个长度为n的字符串A。

第三行包含整数m,表示字符串B的长度。

第四行包含一个长度为m的字符串B。

字符串中均只包含大写字母。

输出格式

输出一个整数,表示最少操作次数。

数据范围

1≤n,m≤10001≤n,m≤1000

输入样例:

10 
AGTCTGACGC
11 
AGTAAGTAGGC

输出样例:

4

 

 

闫式dp.jpg

 

状态表示 dp[i][j]
集合 : 所有吧a中的前i个字母 变成 b中前j个字母的集合的操作集合
属性 : 所有操作中操作次数最少的方案的操作数
状态计算
状态划分 以对a中的第i个字母操作不同划分
在该字母之后添加
添加一个字母之后变得相同,说明没有添加前a的前i个已经和b的前j-1个已经相同
即 : dp[i][j] = dp[i][j-1] + 1
删除该字母
删除该字母之后变得相同,说明没有删除前a中前i-1已经和b的前j个已经相同
即 : dp[i][j] = dp[i-1][j] + 1
替换该字母
替换说明对应结尾字母不同,则看倒数第二个
即: dp[i][j] = dp[i-1][j-1] + 1
啥也不做
对应结尾字母相同,直接比较倒数第二个
即: dp[i][j] = dp[i-1][j-1]

作者:jasonlin
链接:https://www.acwing.com/solution/content/10499/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



import java.io.*;
import java.lang.*;

class Main{
    
    static int n = 0, m = 0, N = 1010;
    static int[][] f = new int[N][N];
    
    public static void main(String[] args)throws Exception{
        BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.valueOf(buf.readLine().trim());
        String sa = buf.readLine();
        m = Integer.valueOf(buf.readLine().trim());
        String sb = buf.readLine();
        //初始化
        for(int i = 0; i <= n; i++) f[i][0] = i;
        for(int i = 0; i <= m; i++) f[0][i] = i;
        for(int i = 1; i <= n; ++i){
            for(int j = 1; j <= m; ++j){
                f[i][j] = Math.min(f[i][j-1], f[i-1][j]) + 1;
                if(sa.charAt(i - 1) == sb.charAt(j - 1)){
                    f[i][j] = Math.min(f[i][j], f[i-1][j-1]);
                }else{
                    f[i][j] = Math.min(f[i][j], f[i-1][j-1] + 1);
                }
            }
        }
        System.out.print(f[n][m]);
        
    }
}

 

<think>好的,用户问动态规划为什么那么难,我需要用中文回答。首先,动态规划确实让很多人头疼,我得分析几个主要原因。 动态规划难,可能因为它的概念比较抽象。比如最优子结构和重叠子问题,这两个术语听起来有点学术,新手可能不太容易理解。得用例子来说明,比如斐波那契数列,重复计算的问题就是重叠子问题,而分解成子问题就是最优子结构。 然后是状态转移方程,这个步骤确实需要经验。用户可能不知道如何定义状态,或者怎么找出状态之间的关系。比如背包问题,怎么把物品和容量转化为状态,再推导方程,这部分可能需要多练习才能掌握。 另外,动态规划的问题类型多,不同问题需要不同的状态定义方法。比如最长公共子序列和最短路径问题,状态的定义方式差别很大,用户可能会觉得没有统一的模式,难以举一反三。 还有,实现的时候细节处理也很关键。比如数组的初始化、边界条件,这些地方容易出错。比如编辑距离问题,初始化第一行和第一列的时候,如果没处理好,结果就会错。 最后,心理因素也可能影响。动态规划被传得很厉害,用户可能一开始就有畏难情绪,导致学习时容易放弃。需要鼓励用户多练习,从简单题入手,逐步积累经验。 总结起来,应该分点解释难点,每个点用例子说明,然后给出学习建议,比如理解基本概念、多练习经典题目、分析题解等。这样用户能更清楚难在哪里,以及如何克服。</think>动态规划(Dynamic Programming)之所以让许多人感到困难,主要原因可以归结为以下几点: --- ### 1. **抽象的概念理解** - **最优子结构**:需要将复杂问题分解为子问题,并理解子问题的最优解如何组合成原问题的最优解。这种“分治+记忆”的思维方式需要一定的数学抽象能力。 - **重叠子问题**:需识别子问题的重复计算(例如斐波那契数列中 `F(n) = F(n-1) + F(n-2)` 的重复调用),而这一特征往往不明显。 - **示例**:经典的背包问题中,需理解“选择当前物品”与“不选当前物品”如何影响后续状态。 --- ### 2. **状态定义与转移方程** - **状态的定义**:如何用变量(如数组或哈希表)描述问题的当前状态(例如 `dp[i][j]` 表示前 `i` 个物品在容量 `j` 下的最大价值)。 - **状态转移方程**:需通过观察和归纳找到状态之间的递推关系(例如 `dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])`),这一步需要较强的逻辑推导能力。 - **难点**:若状态定义不当,可能导致方程复杂甚至无法求解。 --- ### 3. **问题类型多样** - 动态规划适用于多种场景,但不同问题的解决方法差异较大: - **线性 DP**:如最长递增子序列(LIS)。 - **区间 DP**:如矩阵链乘法。 - **树形 DP**:如二叉树中的最大路径和。 - **状态压缩 DP**:如旅行商问题(TSP)。 - 每种类型需要不同的技巧,难以用单一模式套用。 --- ### 4. **实现细节易出错** - **边界条件**:例如初始化 `dp[0]` 或处理索引越界问题。 - **空间优化**:如何将二维 DP 压缩为一维(如背包问题的滚动数组)。 - **遍历顺序**:某些问题需要逆序更新(如 0-1 背包),而某些需要顺序更新(如完全背包)。 --- ### 5. **缺乏直观性** - 动态规划通常需要“逆向思维”,即从最终目标反推初始状态(如编辑距离问题)。 - 与贪心算法不同,动态规划的决策可能依赖历史所有状态,而非仅当前最优。 --- ### **如何克服困难?** 1. **理解基本概念**:从简单问题(如爬楼梯、斐波那契数列)入手,体会重叠子问题和最优子结构。 2. **总结经典模型**:如背包问题、LCS(最长公共子序列)、LIS(最长递增子序列)。 3. **多练习+分析题解**:通过刷题(LeetCode、AcWing)熟悉常见套路,对比他人代码优化思路。 4. **画表格辅助**:手动推导状态转移表(如 `dp[i][j]` 表格)可帮助理解递推过程。 --- 动态规划的难度本质上是“思维模式转换”的挑战,但随着经验积累和模式识别能力提升,会逐渐变得得心应手。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值