线性DP应用及滚动数组优化
线性DP应用及滚动数组优化
题目描述
设 \(A\) 和 \(B\) 是两个字符串。我们要用最少的字符操作次数,将字符串 \(A\) 转换为字符串 \(B\)。这里所说的字符操作共有三种:
- 删除一个字符;
- 插入一个字符;
- 将一个字符改为另一个字符。
\(A, B\) 均只包含小写字母。
典型的动态规划问题,用空间存储已经计算的状态,再来递推未知的状态
-
记状态变量 \(dp_{i,j}\) 表示 \([A_1,A_i]\) 到 \([B_1,B_j]\) 的编辑距离
-
处理边界情况 \(dp_{i,0}=i,dp_{0,j}=j\) 这表示将一个非空串修改到空串的操作数
-
状态转移方程
对于枚举 \(A_i,B_j\) 存在两种情况:\(A_i=B_j\ {\rm{or}}\ A_i\ne B_j\)
第一种情况时,\(A_i\) 已经与 \(B_j\) 配对成功,那么不需要进行操作,所以 \(dp_{i,j}=dp_{i-1,j-1}\) 转移得来
第二种情况,可以执行三种操作
- 删除一个字符即删去 \(A_i\),\(dp_{i,j}=dp_{i-1,j}+1\)
- 插入一个字符即在 \(A_i\) 后插入 \(B_j\) ,\(dp_{i,j}=dp_{i,j-1}+1\)
- 将一个字符改为另一个字符即把 \(A_i\) 改为 \(B_i\) ,\(dp_{i,j}=dp_{i-1,j-1}+1\)
*删去 \(A_i\) 与 在 \(A_i\) 后插入 \(B_j\) 同样的和对操作 \(B\) 等价
综上可以得出状态转移方程
\[dp_{i,j}=\begin{cases}
dp_{i-1,j-1} & \text{$A_i=B_j$}\\\\
{\rm{min}}(dp_{i-1,j},dp_{i,j-1},dp_{i-1,j-1})+1 & \text{$A_i\ne B_j$}
\end{cases}
\]
string a,b; cin>>a>>b; a='#'+a; b='#'+b;//优化下标从1开始 for (int i=1;i<a.size();i++) dp[i][0]=i; for (int j=1;j<b.size();j++) dp[0][j]=j;//边界值初始化 for(int i=1;i<a.size();i++){ for (int j=1;j<b.size();j++){ if (a[i]==b[j]) dp[i][j]=dp[i-1][j-1]; else dp[i][j]=min(dp[i-1][j],min(dp[i-1][j-1],dp[i][j-1]))+1; } } cout<<dp[a.size()-1][b.size()-1];
这样的时间复杂度与空间复杂度都为 \(O(nm)\) ,事实上可以对空间进行再次优化
上图可以看出,\(dp_{i,j}\) 的状态只会与 \(dp_{i-1,j-1},dp_{i-1,j},dp_{i,j-1}\) 有关
可以设置临时变量来记录 \(dp_{i-1,j-1}\) ,重复空间来从旧的 \(dp_j\) 递推到新的 \(dp_j\)
例如
*引自董晓算法
for (int i=1;i<b.size();i++) dp[i]=i;//初始化边界 for(int i=1;i<a.size();i++){ int t1=dp[0]++;//从最左侧开始赋值t1 for (int j=1;j<b.size();j++){ int t2=dp[j];//t2记录旧空间内的dp_j if (a[i]==b[j]) dp[j]=t1; else dp[j]=min(t1,min(dp[j-1],dp[j]))+1;//更新新递推出的dp_j t1=t2;//将t1移动到旧空间内的dp_j上,进行下一次操作 } } cout<<dp[b.size()-1];
分类:
从0.5开始的算法学习记录
, 杂题
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具