ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

经典动态规划问题详细解析:最短编辑距离算法原理分析和解法实现

2022-01-20 22:01:09  阅读:119  来源: 互联网

标签:... int s2 s1 最短 编辑 算法 解法 dp


基本概念

  • 编辑距离问题:
    • 编辑距离问题困难,解法却是很漂亮,而且也是少有的比较实用的算法
  • 编辑距离使用场景:
    • 对于修改文章的错位内容.限制文章只能修改20个字,且支持增,删,替换操作,求修改的最优方案
    • 对于衡量DNA的相似程度 .DNA序列是由A, G, C, T组成的序列,可以类比成字符串,可以通过编辑距离衡量两个DNA序列的相似度,编辑距离越小,说明这两个DNA序列越相似

思路分析

  • 编辑距离问题:
    • 给定两个字符串s1s2, 只能使用3种操作,将s1变成s2, 求最小的操作数
    • 需要保证,不管是将s1变成s2, 还是将s2变成s1, 结果都要是一样的
  • 在最长公共子序列中,解决两个字符串的动态规划问题,一般是使用两个指针i,j分别指向两个字符串的最后,然后一步一步向前走,缩小问题的规模
  • 计算编辑距离:
    • 算法的base case:
      • s1s2字符相同时.为了使编辑距离最小,不需要进行任务操作,直接移动即可
      • 如果j走完s2,i还没有走完s1. 为了使编辑距离最小,只能使用删除操作将s1缩短为s2
      • 如果i走完s1,j还没有走完s2, 为了使编辑距离最小,只能使用插入操作将s1增长为s2

问题解法

  • 算法的base case: i走完s1或者j走完s2, 可以直接返回另一个字符串剩下的长度
  • 对于每一对s1[i] 和s2[j] 有4种操作:
if s1[i] == s2[j]:
	不做操作,直接跳过skip
	i,j同时向前移动
else:
	操作 3 选 1:
		插入-insert
		删除-delete
		替换-replace
复制代码
  • 对于31的操作,可以使用递归技巧,将3个操作全试一遍,然后根据哪个操作得到的编辑距离最小,就选择哪一个操作的结果
def minDistance(s1, s2) -> int:

	# 返回s1[0,..., i]和s2[0, ..., j]的最小编辑距离
	def dp(i, j):
		# base case
		if i == -1: return j + 1 
		if j == -1: return i + 1

		if s1[i] == s2[j]:
			# 不需要进行任务操作
				# s1[0,..., i]和s2[0, ..., j]的最小编辑距离等于s1[0,..., i - 1]和s2[0, ..., j - 1]的最小编辑距离
				# 即dp(i, j)等于dp(i - 1, j - 1) 
			return dp(i - 1, j - 1)
		else:
			return min(
				# 插入
					# 在s1[i]中插入1个和s2[j]一样的字符就可以匹配
					# 将s2的字符前移1位,继续比对
					# 插入操作,操作数加1
				dp(i, j - 1) + 1
				# 删除
					# 将s1[i]中删除这1个字符就可以匹配
					# 将s2的字符前移1位,继续比对
					# 删除操作,操作数加1
				dp(i - 1, j) + 1
				# 替换
					# 将s1[i]替换成s2[j]就可以继续匹配
					# 同时i,j前移1位,继续比对
					# 替换操作,操作数加1
				dp(i - 1, j - 1) + 1
			)
	return dp(len(s1) - 1, len(s2) - 1)
复制代码
  • 该解法存在重叠子问题,需要通过动态规划算法进行优化
  • 如何判断是否存在重叠子问题?
    • 抽象出解法的算法框架
    • 判断子问题到原问题的不同路径
    • 一旦存在重复的路径,就说明存在巨量的重复路径,也就是存在重叠子问题

问题优化

  • 重叠子问题使用动态规划优化的方式有两种:
    • 备忘录
    • DP Table

备忘录

def minDistance(s1, s2) -> int:

# 备忘录
memo = dict()
def dp(i, j):
	if (i, j) in memo:
		return memo[(i, j)]
	...
	if s[i] == s[j]:
		memo[(i, j)] = ...
	else:
		memo[(i, j)] = ...
	return memo[(i, j)]
return dp(len(s1) - 1, len(s2) - 1)
复制代码

DP Table

  • 首先需要理解DP数组的含义 ,DP数组是一个二维数组
    • base case: dp[...][0]和dp[0][...]
    • dp[i][j]的含义:
      • def dp(i, j) -> int: 返回s1[0,...,i] 和s2[0,...,j] 的最小编辑距离
      • dp[i - 1][j - 1]: 存储s1[0,...,i] 和s2[0,...j] 的最小编辑距离. 因为dp函数的i,j的值为 -1, 而数组的最小索引值为0. 所以DP数组会偏移一位
int minDistance(String s1, String s2) {
	int m = s1.length(), n = s2.length();
	int[][] dp = new int[m][n];
	// base case
	for (int i = 1; i <=m; i++) {
		dp[i][0] = i;
	}
	for (int j = 1; j <= n; j++) {
		dp[0][j] = j;
	}
	// 自底向上计算DP Table
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= n; j++) {
			if (s1.isCharAt(i) == s2.isCharAt(j)) {
				dp[i][j] = dp[i - 1][j - 1];
			} else {
				dp = min(
					// 删除
					dp[i - 1][j] + 1;
					// 替换
					dp[i - 1][j - 1] + 1;
					// 插入
					dp[i][j - 1] + 1;
				);
			}
		}
	}
	// 存储s1和s2的最小编辑距离
	return dp[m][n];
}

int min(int a, int b, int c) {
	return Math.min(a, Math.min(b, c));
}
复制代码

总结

  • 处理两个字符串的动态规划问题:
    • 按照编辑距离的算法进行求解
    • 建立DP Table. 这样比较容易找出状态转移关系
  • 因为每一个dp[i][j]只和附近的三个状态有关,所以空间复杂度可以压缩成O(min(m,n)),其中m,n是两个字符串的长度O(min(m,n)),其中m,n是两个字符串的长度
  • 可以给DP Table中增加额外的信息:
Node[][] dp;

class Node {
	// 之前dp数组的数值
	int val;
	// 属性操作
	int choice;
}
复制代码
  • 在做最优选择时,将操作记录下来,然后就可以从结果反推具体操作
  • 最终结果是dp[m][n], 这里的val存在着最小编辑距离 ,choice存在着最后一个操作
  • 重复此过程,可以一步一步回到起点dp[0][0], 形成一条路径,按这条路径进行字符串的编辑,就是最小编辑距离
  • 最小编辑距离Java实现
  • 最小编辑距离C++实现:
class DynamicProgramming {
	public:
		int minDistance(String s1, String s2) {
			int m = s1.size(), n = s2.size();
			vector<vector<int>> dp(m + 1, vector<int>(n + 1));
			// basecase
			// 当字符串s2为空时,字符串s1需要删除所有字符才能和s2相同
			for (int i = 1; i <= m; i++) {
				dp[i][0] = i;
			}
			// 当字符串s1为空时,字符串s2需要删除所有字符才能和s1相同
			for (int j = 1; j <= n; j++) {
				dp[0][j] = j;
			}
			// 自底向上求解
			for (int i = 1; i <= m; i++) {
				for (int j = 1; j <= n; j++) {
					if (s1[i - 1] == s2[j - 1]) {
						// 两个字符串当前的字符相等
						dp[i][j] = dp[i - 1][j - 1];
					} else {
						// 两个字符串当前的字符不相同
						dp[i][j] = min({
							// 删除s1[i]这个字符
							dp[i - 1][j] + 1,
							// 在s1[i]字符后面添加一个与s2[j]相同的字符
							dp[i][j - 1] + 1,
							// 将s1[i]的字符替换为s2[j]的字符
							dp[i - 1][j - 1] + 1
							});
					}
				}
			}
			// 储存整个s1和s2的最小编辑距离
			return dp[m][n];
		}
};

 

标签:...,int,s2,s1,最短,编辑,算法,解法,dp
来源: https://blog.csdn.net/SharingOfficer/article/details/122610886

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有