目录
1. 问题描述
字符串有三种编辑操作:插入一个字符、删除一个字符或者替换一个字符。 给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。
示例 1:
输入: first = "pale" second = "ple" 输出: True
示例 2:
输入: first = "pales" second = "pal" 输出: False
2. 思路与算法1
这个算是编辑距离(参见Leetcode72:Leetcode学习计划之动态规划入门day19(392,1143,72))问题的一个简化版?
既然只要求判断编辑距离是否小于等于1,那是否可以在完整的编辑距离解决方案的基础上做某种简化呢?
不管三七二十一,先上一个完整版的“编辑距离”解决方案。关于算法解释参见以上链接。
class Solution:
def oneEditAway(self, first: str, second: str) -> bool:
m, n = len(first), len(second)
dp = [(n+1)*[0] for _ in range(m+1)] # (m x n) array
dp[0] = list(range(n+1))
for i in range(m+1):
dp[i][0] = i
for i in range(1,m+1):
for j in range(1,n+1):
if first[i-1] == second[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1]) + 1
return dp[m][n]<=1
执行用时:148 ms, 在所有 Python3 提交中击败了5.05%的用户
内存消耗:18.8 MB, 在所有 Python3 提交中击败了5.05%的用户
果然。。。应该是用不上完整编辑距离解决方案。
3. 改进1
根据Leetcode72的解答,我们知道,记表示的前个字母构成的子串word1[:i](pythonic slicing)与的前个字母构成的子串word2[:j]之间的编辑距离的话,存在以下状态转移方程。
dp[i,j]可以表示为如下图所示的2维数组。
结合状态转移方程以及上图不难发现,每个格点内的值不可能小于它偏离对角线格点的“距离”,记(i,j)偏离对角线格点的距离为。
因此事实上只需要关注主对角线以及两条紧邻的副对角线上的情况即可以做出本题所要求的判定了。由此可以缩小搜索范围。但是为了方便搜索,在初始化时按照对dp数组进行初始化。
class Solution:
def oneEditAway(self, first: str, second: str) -> bool:
m, n = len(first), len(second)
dp = [(n+1)*[0] for _ in range(m+1)] # (m x n) array
for i in range(m+1):
for j in range(n+1):
dp[i][j] = abs(i-j)
for i in range(1,m+1):
for j in range(max(i-1,1),min(n+1,i+2)):
dp[i][j] = min(dp[i-1][j-1]+ (0 if (first[i-1] == second[j-1]) else 1),dp[i-1][j]+1,dp[i][j-1]+1)
return dp[m][n]<=1
执行用时:80 ms, 在所有 Python3 提交中击败了6.60%的用户
内存消耗:18.9 MB, 在所有 Python3 提交中击败了5.05%的用户
虽然比上一个解有一定改善(140ms-->80ms),但是依然惨淡。
4. 改进2
追加提前退出机制。
当“dp[i][i]>1 and dp[i][i-1]>1 and dp[i-1][i]>1”满足时,显然后面不用再比较了。为了方便这一判断,预处理时确保第一个字符串较短,第二个字符串较长。
class Solution:
def oneEditAway(self, first: str, second: str) -> bool:
if len(first) <= len(second):
m, n = len(first), len(second)
else:
m, n = len(second), len(first)
first,second = second,first
if n-m > 1:
return False
if n < 1:
return True
dp = [(n+1)*[0] for _ in range(m+1)] # (m x n) array
for i in range(m+1):
for j in range(n+1):
dp[i][j] = abs(i-j)
print(dp)
for i in range(1,m+1):
for j in range(max(i-1,1),min(n+1,i+2)):
dp[i][j] = min(dp[i-1][j-1]+ (0 if (first[i-1] == second[j-1]) else 1),dp[i-1][j]+1,dp[i][j-1]+1)
if dp[i][i]>1 and dp[i][i-1]>1 and dp[i-1][i]>1:
return False
return dp[m][n]<=1
执行用时:96 ms, 在所有 Python3 提交中击败了6.50%的用户
内存消耗:21.2 MB, 在所有 Python3 提交中击败了5.05%的用户
然而,并没有什么效果。
5. 官解
class Solution:
def oneEditAway(self, first: str, second: str) -> bool:
m, n = len(first), len(second)
if m < n:
return self.oneEditAway(second, first)
if m - n > 1:
return False
for i, (x, y) in enumerate(zip(first, second)):
if x != y:
return first[i + 1:] == second[i + 1:] if m == n else first[i + 1:] == second[i:] # 注:改用下标枚举可达到 O(1) 空间复杂度
return True
执行用时:40 ms, 在所有 Python3 提交中击败了67.77%的用户
内存消耗:15 MB, 在所有 Python3 提交中击败了36.02%的用户
回到总目录:Leetcode每日一题总目录(动态更新。。。)