Day48 | 动态规划 :线性DP 编辑距离

Day48 | 动态规划 :线性DP 编辑距离

动态规划应该如何学习?-CSDN博客

本次题解参考自灵神的做法,大家也多多支持灵神的题解

最长公共子序列 编辑距离_哔哩哔哩_bilibili

动态规划学习:

1.思考回溯法(深度优先遍历)怎么写

注意要画树形结构图

2.转成记忆化搜索

看哪些地方是重复计算的,怎么用记忆化搜索给顶替掉这些重复计算

3.把记忆化搜索翻译成动态规划

基本就是1:1转换

文章目录

72.编辑距离

不知道怎么回事从困难题目直接变成中等题目了

72. 编辑距离 - 力扣(LeetCode)

思路分析(子问题):

设两个字符串分别是s和t

对应的最后一个字母分别是x和y

dfs(x,y)那就是s以x结尾,t以y结尾的两个字符串由s变成t的需要的最小操作数了

举个例子

cpp 复制代码
word1 = "horse", word2 = "ros"

x==1,y==2那就是s==ho变到t==ros需要的最小操作数了

可以先看看昨天这篇题解

Day47 | 动态规划 :线性DP 最长公共子序列&&最长公共子数组-CSDN博客

cpp 复制代码
//设两个字符串分别是s和t

//对应的最后一个字母分别是x和y

dfs(x,y)那就是s以x结尾,t以y结尾的两个字符串的最长公共子序列的长度了

那就有四种情况

1.选x这个字母也选y这个字母

2.不选x不选y

3.选x不选y

4.选y不选x

如果s[x]==t[y],那肯定就是选x也选y,那肯定就是

dfs(x,y)=dfs(x-1,y-1)+1

如果说s[x]!=t[y],那么就是剩下三种情况里面取一个最大值

dfs(x,y)=max(dfs(x-1,y-1),dfs(x,y-1),dfs(x-1,y))

再仔细一看,其实

dfs(x,y-1),dfs(x-1,y)包含了dfs(x-1,y-1),所以不需要这个了

dfs(x,y)=max(dfs(x,y-1),dfs(x-1,y))

不明白可以举例
dfs(x,y-1)=max(dfs(x-1,y-1),dfs(x,y-2))
其中包含了x-1,y-1的情况

现在重新思考一下选或不选这个过程

1.s[x]==t[y]

那说明我们什么都不用做,因为这两个字符本身就是相等的,我们不需要删除也不需要插入也不需要替换

cpp 复制代码
dfs(x,y)=dfs(x-1,y-1)

2.s[x]!=t[y]

那我们需要在插入,删除,替换这三种操作中选择一个最小值,选择完最小值之后要+1,因为我们肯定会进行这三种操作的一种

cpp 复制代码
dfs(x,y)=min(dfs(x,y-1),dfs(x-1,y),dfs(x-1,y-1))+1
//			插入			删除			替换

插入操作理解:

在s中插入一个字母z让它和t的第y个字母相同

(可以这么理解:因为是s要变成t,插入是在x的下一位置(x+1处)插入,我们在x+1处插入了t[y]使得这两个字符串相等,所以我们下次递归的时候就不需要看t[y]了,直接从t[y-1]开始,因为t[y]和s[x+1]是相等的)

复制代码
dfs(x,y-1)+1

举个例子

复制代码
s=exection -> t=execution (插入 'u')

从后往前递归,递归到x=3,y=4,即x指向c,y指向u的时候

dfs(x,y)=dfs(3,4)的意思就是

复制代码
exec变成execu需要的最小操作数

dfs(x,y-1)=dfs(3,3)

复制代码
exec变成exec需要的操作数

dfs(x,y)=dfs(x,y-1)+1

意思就是

复制代码
exec变成exec需要的操作数+在s中插入一个u这个操作

删除操作理解:

复制代码
dfs(x-1,y)+1

在s中删除一个字母,就是把和t[y]不一样的那个字母给删掉

和插入同理

(可以这么理解:因为是s要变成t,删除是删s[x],我们在x处删除了s[x],所以我们下次递归的时候就不需要看s[x]了,直接从s[x-1]开始)

举例:

复制代码
s=rorse -> t=rose (删除 'r')

那么

dfs(2,1)

复制代码
s=ror -> t=ro (删除 'r')

dfs(1,1)+1

复制代码
s=ro -> t=ro 

替换操作理解:

复制代码
dfs(x-1,y-1)+1

把s[x]换成和t[y]一样的字母

这个比较简明,就是用y替换掉了x,然后就都不看这两个了,直接去看两个字符串前面了

1.回溯 DFS

1.返回值和参数

dfs(i,j)那就是s以i结尾,t以j结尾的两个字符串由s变成t的需要的最小操作数了

cpp 复制代码
int dfs(int i,int j,string &s,string &t)

2.终止条件

s为空字符串,那s就需要插入j+1个字符变成t

t为空字符串,那么s就需要删除i+1个字符变成t

cpp 复制代码
		if(i<0)
            return j+1;
        if(j<0)
            return i+1;

3.本层逻辑

相等就不需要操作,不相等就三种操作里面选最小值

cpp 复制代码
		if(s[i]==t[j])
            return dfs(i-1,j-1,s,t);
        else
            return min(min(dfs(i,j-1,s,t),dfs(i-1,j,s,t)),dfs(i-1,j-1,s,t))+1;

完整代码:

当然,这是超时的

cpp 复制代码
class Solution {
public:
    int dfs(int i,int j,string &s,string &t,vector<vector<int>>& dp)
    {
        if(i<0)
            return j+1;
        if(j<0)
            return i+1;
        if(dp[i][j]!=-1)
            return dp[i][j];
        if(s[i]==t[j])
            return dp[i][j]=dfs(i-1,j-1,s,t,dp);
        else
            return dp[i][j]=min(min(dfs(i,j-1,s,t,dp),dfs(i-1,j,s,t,dp)),dfs(i-1,j-1,s,t,dp))+1;
    }
    int minDistance(string word1, string word2) {
        vector<vector<int>> dp(word1.size(),vector<int>(word2.size(),-1));
        return dfs(word1.size()-1,word2.size()-1,word1,word2,dp);
    }
};
cpp 复制代码
//lambda
class Solution {
public:
    int minDistance(string word1, string word2) {
        function<int(int,int)> dfs=[&](int i,int j)->int{
            if(i<0)
            return j+1;
            if(j<0)
                return i+1;
            if(word1[i]==word2[j])
                return dfs(i-1,j-1);
            else
                return min(min(dfs(i,j-1),dfs(i-1,j)),dfs(i-1,j-1))+1;
        }; 
        return dfs(word1.size()-1,word2.size()-1);
    }
};

2.记忆化搜索

就是搞一个哈希表dp,全都初始化为-1,每次返回前给哈希表dp赋值,碰到不是-1的那就是算过的,那就直接返回计算过的结果,不需要再次递归了

cpp 复制代码
class Solution {
public:
    int dfs(int i,int j,string &s,string &t,vector<vector<int>> &dp)
    {
        if(i<0||j<0)
            return 0;
        if(dp[i][j]!=-1)
            return dp[i][j];
        if(s[i]==t[j])
            return dp[i][j]=dfs(i-1,j-1,s,t,dp)+1;
        else
            return dp[i][j]=max(dfs(i-1,j,s,t,dp),dfs(i,j-1,s,t,dp));
    }
    int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> dp(text1.size(),vector<int>(text2.size(),-1));
        return dfs(text1.size()-1,text2.size()-1,text1,text2,dp);
    }
};
cpp 复制代码
//lambda
class Solution {
public:
    int minDistance(string word1, string word2) {
        vector<vector<int>> dp(word1.size(),vector<int>(word2.size(),-1));
        function<int(int,int)> dfs=[&](int i,int j)->int{
            if(i<0)
            return j+1;
            if(j<0)
                return i+1;
            if(dp[i][j]!=-1)
                return dp[i][j];
            if(word1[i]==word2[j])
                return dp[i][j]=dfs(i-1,j-1);
            else
                return dp[i][j]=min(min(dfs(i,j-1),dfs(i-1,j)),dfs(i-1,j-1))+1;
        }; 
        return dfs(word1.size()-1,word2.size()-1);
    }
};

3.1:1翻译为动态规划

1.确定dp数组以及下标的含义

dp[i][j]就是dfs(I,j)

下标从1开始,下标0记录对应回溯终止条件处的赋值

2.确定递推公式

和dfs中也是对应的

cpp 复制代码
				if(word1[i]==word2[j])
                    dp[i+1][j+1]=dp[i][j];
                else    
                    dp[i+1][j+1]=min(min(dp[i+1][j],dp[i][j+1]),dp[i][j])+1;

3.dp数组如何初始化

根据回溯终止条件进行初始化

s从空变到t就是插入i哥

t为空,s变过去就是删除i个

注意我们的下标是从1开始的,所以初始化时候要写小于等于,不然会有两个值没有初始化导致报错

cpp 复制代码
		vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1));
        for(int i=0;i<=word2.size();i++)
            dp[0][i]=i;
        for(int i=0;i<=word1.size();i++)
            dp[i][0]=i;

4.确定遍历顺序

cpp 复制代码
		for(int i=0;i<word1.size();i++)
            for(int j=0;j<word2.size();j++)
                if(word1[i]==word2[j])
                    dp[i+1][j+1]=dp[i][j];
                else    
                    dp[i+1][j+1]=min(min(dp[i+1][j],dp[i][j+1]),dp[i][j])+1;

完整代码

cpp 复制代码
class Solution {
public:
    int minDistance(string word1, string word2) {
        vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1));
        for(int i=0;i<=word2.size();i++)
            dp[0][i]=i;
        for(int i=0;i<=word1.size();i++)
            dp[i][0]=i;
        for(int i=0;i<word1.size();i++)
            for(int j=0;j<word2.size();j++)
                if(word1[i]==word2[j])
                    dp[i+1][j+1]=dp[i][j];
                else    
                    dp[i+1][j+1]=min(min(dp[i+1][j],dp[i][j+1]),dp[i][j])+1;
        
        return dp[word1.size()][word2.size()];
    }
};
相关推荐
业精于勤的牙9 分钟前
三角形最小路径和(二)
算法
风筝在晴天搁浅11 分钟前
hot100 239.滑动窗口最大值
数据结构·算法·leetcode
夏乌_Wx22 分钟前
练题100天——DAY31:相对名次+数组拆分+重塑矩阵
数据结构·算法
LYFlied23 分钟前
【算法解题模板】-解二叉树相关算法题的技巧
前端·数据结构·算法·leetcode
Ven%1 小时前
【AI大模型算法工程师面试题解析与技术思考】
人工智能·python·算法
天勤量化大唯粉1 小时前
枢轴点反转策略在铜期货中的量化应用指南(附天勤量化代码)
ide·python·算法·机器学习·github·开源软件·程序员创富
爱学习的小仙女!1 小时前
算法效率的度量 时间复杂度 空间复杂度
数据结构·算法
AndrewHZ1 小时前
【复杂网络分析】什么是图神经网络?
人工智能·深度学习·神经网络·算法·图神经网络·复杂网络
Swizard1 小时前
拒绝“狗熊掰棒子”!用 EWC (Elastic Weight Consolidation) 彻底终结 AI 的灾难性遗忘
python·算法·ai·训练
fab 在逃TDPIE2 小时前
Sentaurus TCAD 仿真教程(十)
算法